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/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/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 23d0923658..a46920fb0b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,8 +16,8 @@ # >> Configure MINIO NATIVES SNAPSHOT # OBJECTS_KEY=XXXXXX # >> Configure SONATYPE RELEASE -# OSSRH_PASSWORD=XXXXXX -# OSSRH_USERNAME=XXXXXX +# CENTRAL_PASSWORD=XXXXXX +# CENTRAL_USERNAME=XXXXXX # >> Configure SIGNING # SIGNING_KEY=XXXXXX # SIGNING_PASSWORD=XXXXXX @@ -46,32 +46,129 @@ on: push: branches: - master + - v3.7 - v3.6 - v3.5 - v3.4 - v3.3 + - ios-2024_2 pull_request: release: types: [published] jobs: + ScreenshotTests: + name: Run Screenshot Tests + runs-on: ubuntu-latest + container: + 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@v4 + with: + fetch-depth: 1 + + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v3 + + - name: Build + run: | + ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \ + :jme3-ios-native:build + + - name: Upload natives + uses: actions/upload-artifact@master + with: + 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-latest container: - image: jmonkeyengine/buildenv-jme3:android + image: ghcr.io/cirruslabs/android-sdk:36-ndk steps: - name: Clone the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 + + - name: Setup Java 11 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '11' + + - name: Check java version + run: java -version + + - name: Install CMake + run: | + apt-get update + apt-get install -y cmake + cmake --version + - name: Validate the Gradle wrapper - uses: gradle/wrapper-validation-action@v1.0.5 + 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 @@ -81,39 +178,39 @@ jobs: name: android-natives path: build/native - # Build the engine, we only deploy from ubuntu-latest jdk17 + # Build the engine, we only deploy from ubuntu-latest jdk21 BuildJMonkey: - needs: [BuildAndroidNatives] + needs: [BuildAndroidNatives, BuildIosNatives] name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu-latest,windows-2019,macOS-latest] - jdk: [8, 11, 17] + os: [ubuntu-latest,windows-latest,macOS-latest] + jdk: [11, 17, 21] include: - os: ubuntu-latest osName: linux deploy: true - - os: windows-2019 + - os: windows-latest osName: windows deploy: false - os: macOS-latest osName: mac deploy: false - - jdk: 8 - deploy: false - jdk: 11 deploy: false + - jdk: 17 + deploy: false steps: - name: Clone the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Setup the java environment - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: ${{ matrix.jdk }} @@ -124,13 +221,20 @@ jobs: 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: Validate the Gradle wrapper - uses: gradle/wrapper-validation-action@v1.0.5 + uses: gradle/actions/wrapper-validation@v3 - name: Build Engine shell: bash run: | - # Build - ./gradlew -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 @@ -138,9 +242,6 @@ jobs: sudo apt-get update sudo apt-get install -y zip - # Create the zip release and the javadoc - ./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true mergedJavadoc createZipDistribution - # We prepare the release for deploy mkdir -p ./dist/release/ mv build/distributions/*.zip dist/release/ @@ -301,16 +402,16 @@ jobs: # We need to clone everything again for uploadToMaven.sh ... - name: Clone the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - # Setup jdk 17 used for building Maven-style artifacts + # Setup jdk 21 used for building Maven-style artifacts - name: Setup the java environment - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' - name: Download natives for android uses: actions/download-artifact@master @@ -318,16 +419,22 @@ jobs: name: android-natives path: build/native - - name: Rebuild the maven artifacts and deploy them to the Sonatype repository + - 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.OSSRH_PASSWORD }}" = "" ]; + if [ "${{ secrets.CENTRAL_PASSWORD }}" = "" ]; then - echo "Configure the following secrets to enable deployment to Sonatype:" - echo "OSSRH_PASSWORD, OSSRH_USERNAME, SIGNING_KEY, SIGNING_PASSWORD" + echo "Configure the following secrets to enable uploading to Sonatype:" + echo "CENTRAL_PASSWORD, CENTRAL_USERNAME, SIGNING_KEY, SIGNING_PASSWORD" else ./gradlew publishMavenPublicationToSNAPSHOTRepository \ - -PossrhPassword=${{ secrets.OSSRH_PASSWORD }} \ - -PossrhUsername=${{ secrets.OSSRH_USERNAME }} \ + -PcentralPassword=${{ secrets.CENTRAL_PASSWORD }} \ + -PcentralUsername=${{ secrets.CENTRAL_USERNAME }} \ -PsigningKey='${{ secrets.SIGNING_KEY }}' \ -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ -PuseCommitHashAsVersionName=true \ @@ -343,18 +450,18 @@ jobs: if: github.event_name == 'release' steps: - # We need to clone everything again for uploadToMaven.sh ... + # We need to clone everything again for uploadToCentral.sh ... - name: Clone the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - # Setup jdk 17 used for building Sonatype OSSRH artifacts + # Setup jdk 21 used for building Sonatype artifacts - name: Setup the java environment - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' # Download all the stuff... - name: Download maven artifacts @@ -375,20 +482,29 @@ jobs: name: android-natives path: build/native - - name: Rebuild the maven artifacts and deploy them to Sonatype OSSRH + - 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.OSSRH_PASSWORD }}" = "" ]; + if [ "${{ secrets.CENTRAL_PASSWORD }}" = "" ]; then - echo "Configure the following secrets to enable deployment to Sonatype:" - echo "OSSRH_PASSWORD, OSSRH_USERNAME, SIGNING_KEY, SIGNING_PASSWORD" + echo "Configure the following secrets to enable uploading to Sonatype:" + echo "CENTRAL_PASSWORD, CENTRAL_USERNAME, SIGNING_KEY, SIGNING_PASSWORD" else - ./gradlew publishMavenPublicationToOSSRHRepository \ - -PossrhPassword=${{ secrets.OSSRH_PASSWORD }} \ - -PossrhUsername=${{ secrets.OSSRH_USERNAME }} \ - -PsigningKey='${{ secrets.SIGNING_KEY }}' \ - -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ - -PuseCommitHashAsVersionName=true \ - --console=plain --stacktrace + ./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 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 150b0d492f..121fd33ac3 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,6 @@ appveyor.yml javadoc_deploy javadoc_deploy.pub !.vscode/settings.json -!.vscode/JME_style.xml \ No newline at end of file +!.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 index 7e54d4f76f..b4014b077e 100644 --- a/.vscode/JME_style.xml +++ b/.vscode/JME_style.xml @@ -43,7 +43,7 @@ - + @@ -89,7 +89,7 @@ - + @@ -220,7 +220,7 @@ - + @@ -264,7 +264,7 @@ - + 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 index 274005bb2e..89d691dd52 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +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": 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/LICENSE.md b/LICENSE.md index 0d6db16108..bb12c971e2 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2009-2023 jMonkeyEngine. +Copyright (c) 2009-2025 jMonkeyEngine. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/README.md b/README.md index 26d0e652f4..923eeb6480 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,19 @@ jMonkeyEngine [![Build Status](https://github.com/jMonkeyEngine/jmonkeyengine/workflows/Build%20jMonkeyEngine/badge.svg)](https://github.com/jMonkeyEngine/jmonkeyengine/actions) jMonkeyEngine is a 3-D game engine for adventurous Java developers. It’s open-source, cross-platform, and cutting-edge. -v3.6.0 is the latest stable version of the engine. +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) + - [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/) @@ -24,27 +24,34 @@ The engine is used by several commercial game studios and computer-science cours - [Leap](https://gamejolt.com/games/leap/313308) - [Jumping Jack Flag](http://timealias.bplaced.net/jack/) - [PapaSpace Flight Simulation](https://www.papaspace.at/) - - [Cubic Nightmare](https://jaredbgreat.itch.io/cubic-nightmare) + - [Cubic Nightmare (on Itch)](https://jaredbgreat.itch.io/cubic-nightmare) - [Chatter Games](https://chatter-games.com) - [Exotic Matter](https://exoticmatter.io) - - [Demon Lord](https://play.google.com/store/apps/details?id=com.dreiInitiative.demonLord&pli=1) - - [Wild Magic](http://wildmagicgame.ru/) + - [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 +## 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 - -Plus a bunch of awesome libraries & tight integrations like Bullet, NiftyGUI and other goodies. + - 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) ### Documentation @@ -58,3 +65,47 @@ Read our [contribution guide](https://github.com/jMonkeyEngine/jmonkeyengine/blo [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/build.gradle b/build.gradle index 2a7b4ed0c5..d4bd67720d 100644 --- a/build.gradle +++ b/build.gradle @@ -10,9 +10,9 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0' - classpath 'me.tatarka:gradle-retrolambda:3.7.1' - classpath "gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.5.1" + classpath libs.android.build.gradle + classpath libs.gradle.retrolambda + classpath libs.spotbugs.gradle.plugin } } @@ -49,6 +49,7 @@ subprojects { // Currently we only warn about issues and try to fix them as we go, but those aren't mission critical. spotbugs { ignoreFailures = true + toolVersion = '4.8.6' } tasks.withType(com.github.spotbugs.snom.SpotBugsTask ) { @@ -74,23 +75,25 @@ task libDist(dependsOn: subprojects.build, description: 'Builds and copies the e subprojects.each {project -> if(!project.hasProperty('mainClassName')){ project.tasks.withType(Jar).each {archiveTask -> - if(archiveTask.classifier == "sources"){ + 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} } } } @@ -114,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' @@ -245,50 +248,8 @@ if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { } } - -//class IncrementalReverseTask extends DefaultTask { -// @InputDirectory -// def File inputDir -// -// @OutputDirectory -// def File outputDir -// -// @Input -// def inputProperty -// -// @TaskAction -// void execute(IncrementalTaskInputs inputs) { -// println inputs.incremental ? "CHANGED inputs considered out of date" : "ALL inputs considered out of date" -// inputs.outOfDate { change -> -// println "out of date: ${change.file.name}" -// def targetFile = new File(outputDir, change.file.name) -// targetFile.text = change.file.text.reverse() -// } -// -// inputs.removed { change -> -// println "removed: ${change.file.name}" -// def targetFile = new File(outputDir, change.file.name) -// targetFile.delete() -// } -// } -//} - -//allprojects { -// tasks.withType(JavaExec) { -// enableAssertions = true // false by default -// } -// tasks.withType(Test) { -// enableAssertions = true // true by default -// } -//} - -wrapper { - gradleVersion = '7.6' -} - - retrolambda { javaVersion JavaVersion.VERSION_1_7 incremental true jvmArgs '-noverify' -} \ No newline at end of file +} diff --git a/common.gradle b/common.gradle index e97fe498ad..042d88e3b5 100644 --- a/common.gradle +++ b/common.gradle @@ -7,14 +7,18 @@ apply plugin: 'groovy' 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 = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} tasks.withType(JavaCompile) { // compile-time options: //options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings @@ -25,11 +29,6 @@ tasks.withType(JavaCompile) { // compile-time options: } } -ext { - lwjgl3Version = '3.3.2' // used in both the jme3-lwjgl3 and jme3-vr build scripts - niftyVersion = '1.4.3' // used in both the jme3-niftygui and jme3-examples build scripts -} - repositories { mavenCentral() flatDir { @@ -39,9 +38,9 @@ repositories { dependencies { // Adding dependencies here will add the dependencies to each subproject. - testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:3.12.4' - testImplementation 'org.codehaus.groovy:groovy-test:3.0.17' + 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 @@ -83,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 } @@ -158,32 +157,40 @@ publishing { 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('ossrhUsername') ? ossrhUsername : 'Unknown user' - password = gradle.rootProject.hasProperty('ossrhPassword') ? ossrhPassword : 'Unknown password' + username = gradle.rootProject.hasProperty('centralUsername') ? centralUsername : 'Unknown user' + password = gradle.rootProject.hasProperty('centralPassword') ? centralPassword : 'Unknown password' } - name = 'OSSRH' - url = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2' + 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/' } - maven { - credentials { - username = gradle.rootProject.hasProperty('ossrhUsername') ? ossrhUsername : 'Unknown user' - password = gradle.rootProject.hasProperty('ossrhPassword') ? ossrhPassword : 'Unknown password' - } - name = 'SNAPSHOT' - url = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' - } } } publishToMavenLocal.doLast { - println 'published ' + project.getName() + "-${jmeFullVersion} to mavenLocal" + println 'published ' + project.getName() + "-${jmeFullVersion} to mavenLocal" } task('install') { dependsOn 'publishToMavenLocal' @@ -200,3 +207,24 @@ signing { 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 3f377cdfb6..42b02b8708 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Version number: Major.Minor.SubMinor (e.g. 3.3.0) -jmeVersion = 3.7.0 +jmeVersion = 3.9.0 # Leave empty to autogenerate # (use -PjmeVersionName="myVersion" from commandline to specify a custom version name ) 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 943f0cbfa7..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 f398c33c4b..a4413138c9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d65..b740cf1339 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85bee..7101f8e467 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/jme3-android-examples/build.gradle b/jme3-android-examples/build.gradle index 5bc2d1cecd..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. @@ -41,8 +41,8 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation 'junit:junit:4.13.2' - implementation 'com.android.support:appcompat-v7:28.0.0' + testImplementation libs.junit4 + implementation libs.android.support.appcompat implementation project(':jme3-core') implementation project(':jme3-android') @@ -54,5 +54,4 @@ dependencies { implementation project(':jme3-plugins') implementation project(':jme3-terrain') implementation fileTree(dir: '../jme3-examples/build/libs', include: ['*.jar'], exclude: ['*sources*.*']) -// compile project(':jme3-examples') } diff --git a/jme3-android-native/bufferallocator.gradle b/jme3-android-native/bufferallocator.gradle index f03027e275..d10335a7f2 100644 --- a/jme3-android-native/bufferallocator.gradle +++ b/jme3-android-native/bufferallocator.gradle @@ -44,6 +44,9 @@ task copyPreCompiledLibsBufferAllocator(type: Copy) { from file(bufferAllocatorPreCompiledLibsDir) into file(bufferAllocatorBuildLibsDir) } +if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { + copyPreCompiledLibsBufferAllocator.dependsOn(rootProject.extractPrebuiltNatives) +} // ndkExists is a boolean from the build.gradle in the root project // buildNativeProjects is a string set to "true" diff --git a/jme3-android-native/build.gradle b/jme3-android-native/build.gradle index cc78ab4722..5ec15daea1 100644 --- a/jme3-android-native/build.gradle +++ b/jme3-android-native/build.gradle @@ -28,11 +28,8 @@ ext { exclude ".gradle" }.asPath } -//println "projectClassPath = " + projectClassPath // add each native lib build file apply from: file('openalsoft.gradle') -// apply from: file('stb_image.gradle') -// apply from: file('tremor.gradle') apply from: file('decode.gradle') apply from: file('bufferallocator.gradle') diff --git a/jme3-android-native/decode.gradle b/jme3-android-native/decode.gradle index 27dc02dd5b..d6a3842f02 100644 --- a/jme3-android-native/decode.gradle +++ b/jme3-android-native/decode.gradle @@ -83,6 +83,9 @@ task copyPreCompiledLibs(type: Copy) { from sourceDir into outputDir } +if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { + copyPreCompiledLibs.dependsOn(rootProject.extractPrebuiltNatives) +} // ndkExists is a boolean from the build.gradle in the root project // buildNativeProjects is a string set to "true" diff --git a/jme3-android-native/openalsoft.gradle b/jme3-android-native/openalsoft.gradle index 8c224ec457..0a14d4b429 100644 --- a/jme3-android-native/openalsoft.gradle +++ b/jme3-android-native/openalsoft.gradle @@ -1,12 +1,12 @@ // 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.21.1.zip' +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-1.21.1' +String openALSoftFolder = 'openal-soft-1.24.3' //Working directories for the ndk build. String openalsoftBuildDir = "${buildDir}" + File.separator + 'openalsoft' @@ -81,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) { @@ -111,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" @@ -137,3 +230,4 @@ class MyDownload extends DefaultTask { ant.get(src: sourceUrl, dest: target) } } + diff --git a/jme3-android-native/src/native/jme_bufferallocator/Application.mk b/jme3-android-native/src/native/jme_bufferallocator/Application.mk index 4bcc2ef85e..de0aea4497 100644 --- a/jme3-android-native/src/native/jme_bufferallocator/Application.mk +++ b/jme3-android-native/src/native/jme_bufferallocator/Application.mk @@ -36,4 +36,6 @@ APP_PLATFORM := android-19 # change this to 'debug' to see android logs 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/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 25a15f5b8a..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" diff --git a/jme3-android-native/src/native/jme_openalsoft/Android.mk b/jme3-android-native/src/native/jme_openalsoft/Android.mk index 13e1547aad..d1f38c1864 100644 --- a/jme3-android-native/src/native/jme_openalsoft/Android.mk +++ b/jme3-android-native/src/native/jme_openalsoft/Android.mk @@ -1,103 +1,54 @@ -TARGET_PLATFORM := android-19 +# jni/Android.mk LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE := openalsoftjme - -LOCAL_C_INCLUDES += $(LOCAL_PATH) $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/alc $(LOCAL_PATH)/common +# 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_CPP_FEATURES += exceptions +# assemble the path to this ABI's .a +OPENAL_PREBUILT_DIR := $(OPENALSOFT_BUILD_ROOT)/cmake-build-$(TARGET_ARCH_ABI) -LOCAL_CFLAGS := -ffast-math -DAL_BUILD_LIBRARY -DAL_ALEXT_PROTOTYPES -fcommon -O0 -DRESTRICT="" -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 := al/auxeffectslot.cpp \ - al/buffer.cpp \ - al/effect.cpp \ - al/effects/autowah.cpp \ - al/effects/chorus.cpp \ - al/effects/compressor.cpp \ - al/effects/convolution.cpp \ - al/effects/dedicated.cpp \ - al/effects/distortion.cpp \ - al/effects/echo.cpp \ - al/effects/equalizer.cpp \ - al/effects/fshifter.cpp \ - al/effects/modulator.cpp \ - al/effects/null.cpp \ - al/effects/pshifter.cpp \ - al/effects/reverb.cpp \ - al/effects/vmorpher.cpp \ - al/error.cpp \ - al/event.cpp \ - al/extension.cpp \ - al/filter.cpp \ - al/listener.cpp \ - al/source.cpp \ - al/state.cpp \ - alc/alc.cpp \ - alc/alconfig.cpp \ - alc/alu.cpp \ - alc/backends/base.cpp \ - alc/backends/loopback.cpp \ - alc/backends/null.cpp \ - alc/backends/opensl.cpp \ - alc/backends/wave.cpp \ - alc/bformatdec.cpp \ - alc/buffer_storage.cpp \ - alc/converter.cpp \ - alc/effects/autowah.cpp \ - alc/effects/chorus.cpp \ - alc/effects/compressor.cpp \ - alc/effects/convolution.cpp \ - alc/effects/dedicated.cpp \ - alc/effects/distortion.cpp \ - alc/effects/echo.cpp \ - alc/effects/equalizer.cpp \ - alc/effects/fshifter.cpp \ - alc/effects/modulator.cpp \ - alc/effects/null.cpp \ - alc/effects/pshifter.cpp \ - alc/effects/reverb.cpp \ - alc/effects/vmorpher.cpp \ - alc/effectslot.cpp \ - alc/helpers.cpp \ - alc/hrtf.cpp \ - alc/panning.cpp \ - alc/uiddefs.cpp \ - alc/voice.cpp \ - common/alcomplex.cpp \ - common/alfstream.cpp \ - common/almalloc.cpp \ - common/alstring.cpp \ - common/dynload.cpp \ - common/polyphase_resampler.cpp \ - common/ringbuffer.cpp \ - common/strutils.cpp \ - common/threads.cpp \ - core/ambdec.cpp \ - core/bs2b.cpp \ - core/bsinc_tables.cpp \ - core/cpu_caps.cpp \ - core/devformat.cpp \ - core/except.cpp \ - core/filters/biquad.cpp \ - core/filters/nfc.cpp \ - core/filters/splitter.cpp \ - core/fmt_traits.cpp \ - core/fpu_ctrl.cpp \ - core/logging.cpp \ - core/mastering.cpp \ - core/mixer/mixer_c.cpp \ - core/uhjfilter.cpp \ - 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) -# Alc/mixer/hrtf_inc.c \ - diff --git a/jme3-android-native/src/native/jme_openalsoft/Application.mk b/jme3-android-native/src/native/jme_openalsoft/Application.mk index d269117ddd..58561d1d14 100644 --- a/jme3-android-native/src/native/jme_openalsoft/Application.mk +++ b/jme3-android-native/src/native/jme_openalsoft/Application.mk @@ -1,5 +1,6 @@ APP_PLATFORM := android-19 APP_OPTIM := release -APP_ABI := all +APP_ABI := armeabi-v7a,arm64-v8a,x86,x86_64 APP_STL := c++_static +APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true diff --git a/jme3-android/build.gradle b/jme3-android/build.gradle index f0725dfc5e..4fa73b50ca 100644 --- a/jme3-android/build.gradle +++ b/jme3-android/build.gradle @@ -2,8 +2,8 @@ apply plugin: 'java' dependencies { //added annotations used by JmeSurfaceView. - compileOnly 'androidx.annotation:annotation:1.3.0' - compileOnly 'androidx.lifecycle:lifecycle-common:2.4.0' + compileOnly libs.androidx.annotation + compileOnly libs.androidx.lifecycle.common api project(':jme3-core') compileOnly 'android:android' } diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java index c9b4dac5e2..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 @@ -45,6 +45,7 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo { IntBuffer tmpBuff = BufferUtils.createIntBuffer(1); + IntBuffer tmpBuff16 = BufferUtils.createIntBuffer(16); @Override public void resetStats() { @@ -162,6 +163,11 @@ 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); @@ -694,10 +700,17 @@ 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 @@ -729,5 +742,21 @@ public void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int 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/system/android/JmeAndroidSystem.java b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java index 0d6ee82c6d..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,7 +17,7 @@ import com.jme3.system.*; import com.jme3.system.JmeContext.Type; import com.jme3.util.AndroidScreenshots; -import com.jme3.util.functional.VoidFunction; +import com.jme3.util.res.Resources; import java.io.File; import java.io.IOException; @@ -52,7 +52,7 @@ public JmeAndroidSystem(){ @Override public URL getPlatformAssetConfigURL() { - return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg"); + return Resources.getResource("com/jme3/asset/Android.cfg"); } @Override 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 5757368ef8..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-2023 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,7 +58,7 @@ import com.jme3.renderer.opengl.*; import com.jme3.system.*; import com.jme3.util.BufferAllocatorFactory; -import com.jme3.util.AndroidNativeBufferAllocator; +import com.jme3.util.PrimitiveAllocator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -78,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, AndroidNativeBufferAllocator.class.getName()); + System.setProperty(implementation, PrimitiveAllocator.class.getName()); } } - public OGLESContext() { - } + public OGLESContext() {} @Override public Type getType() { @@ -114,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"); } @@ -126,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(); } } @@ -136,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); @@ -199,22 +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() { - @Override - 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(); GL gl = new AndroidGL(); if (settings.getBoolean("GraphicsDebug")) { - gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GLES_30.class, GLFbo.class, GLExt.class); + gl = + (GL) GLDebug.createProxy( + gl, + gl, + GL.class, + GL2.class, + GLES_30.class, + GLFbo.class, + GLExt.class + ); } if (settings.getBoolean("GraphicsTrace")) { - gl = (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, (GLExt)gl, (GLFbo)gl); + renderer = new GLRenderer(gl, (GLExt) gl, (GLFbo) gl); renderer.initialize(); JmeSystem.setSoftTextDialogInput(this); @@ -233,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."); @@ -253,7 +276,7 @@ public void setSettings(AppSettings settings) { } if (settings.getFrameRate() > 0) { - minFrameDuration = (long)(1000d / settings.getFrameRate()); // ms + minFrameDuration = (long) (1000d / settings.getFrameRate()); // ms logger.log(Level.FINE, "Setting min tpf: {0}ms", minFrameDuration); } else { minFrameDuration = 0; @@ -311,8 +334,7 @@ public Timer getTimer() { } @Override - public void setTitle(String title) { - } + public void setTitle(String title) {} @Override public boolean isCreated() { @@ -328,7 +350,11 @@ public void setAutoFlushFrames(boolean enabled) { @Override public void onSurfaceChanged(GL10 gl, int width, int height) { if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "GL Surface changed, width: {0} height: {1}", new Object[]{width, height}); + 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); @@ -371,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(); - } } @@ -401,8 +425,7 @@ public void create() { } @Override - public void restart() { - } + public void restart() {} @Override public void destroy(boolean waitFor) { @@ -420,76 +443,99 @@ protected void waitFor(boolean createdVal) { while (renderable.get() != createdVal) { try { Thread.sleep(10); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } } @Override - public void requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener) { + 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}); + 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; - - 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; + 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(); + } } - - 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(); - } - }); + ); } @Override @@ -541,11 +587,11 @@ public int getWindowXPosition() { public int getWindowYPosition() { throw new UnsupportedOperationException("not implemented yet"); } - + /** * Retrieves the dimensions of the input surface. Note: do not modify the * returned object. - * + * * @return the dimensions (in pixels, left and top are 0) */ private Rect getSurfaceFrame() { @@ -554,4 +600,16 @@ private Rect getSurfaceFrame() { Rect result = holder.getSurfaceFrame(); return result; } + + @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-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java index d364ab0923..67d73f4b5c 100644 --- a/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -239,11 +239,7 @@ public void startRenderer(int delayMillis) { } private void removeGLSurfaceView() { - ((Activity) getContext()).runOnUiThread(() -> { - if (glSurfaceView != null) { - JmeSurfaceView.this.removeView(glSurfaceView); - } - }); + ((Activity) getContext()).runOnUiThread(() -> JmeSurfaceView.this.removeView(glSurfaceView)); } @Override @@ -265,19 +261,34 @@ public void handleError(String errorMsg, Throwable throwable) { public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { switch (event) { case ON_DESTROY: - /*destroy only if the policy flag is enabled*/ - if (destructionPolicy == DestructionPolicy.DESTROY_WHEN_FINISH) { - legacyApplication.stop(!isGLThreadPaused()); - } + // 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: - loseFocus(); + // 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: - jmeSurfaceViewLogger.log(Level.INFO, "Context stops, but game is still running"); + // activity is out off the foreground stack or being destructed by a finishing dispatch + // this is a killable automata state! break; } } @@ -404,13 +415,13 @@ public void loseFocus() { @Override public void destroy() { - /*skip the destroy block if the invoking instance is null*/ - if (legacyApplication == null) { - return; + if (glSurfaceView != null) { + removeGLSurfaceView(); + } + if (legacyApplication != null) { + legacyApplication.destroy(); } - removeGLSurfaceView(); - legacyApplication.destroy(); - /*help the Dalvik Garbage collector to destruct the pointers, by making them nullptr*/ + /*help the Dalvik Garbage collector to destruct the objects, by releasing their references*/ /*context instances*/ legacyApplication = null; appSettings = null; @@ -430,10 +441,10 @@ public void destroy() { onRendererCompleted = null; onExceptionThrown = null; onLayoutDrawn = null; - /*nullifying the static memory (pushing zero to registers to prepare for a clean use)*/ GameState.setLegacyApplication(null); GameState.setFirstUpdatePassed(false); - jmeSurfaceViewLogger.log(Level.INFO, "Context and Game have been destructed"); + JmeAndroidSystem.setView(null); + jmeSurfaceViewLogger.log(Level.INFO, "Context and Game have been destructed."); } @Override @@ -516,11 +527,13 @@ public void bindAppStateToActivityLifeCycle(final boolean 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."); } } } @@ -917,7 +930,7 @@ public void setShowErrorDialog(boolean showErrorDialog) { } /** - * Determines whether the app context would be destructed + * 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}. @@ -926,12 +939,14 @@ public void setShowErrorDialog(boolean showErrorDialog) { */ public enum DestructionPolicy { /** - * Finishes the game context with the activity context (ignores the static memory {@link GameState#legacyApplication}). + * 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, but the app stills in the background. + * when the activity context is destroyed dispatching {@link Activity#finish()}, but the {@link android.app.Application} + * stills in the background. */ KEEP_WHEN_FINISH } 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 index 34744b2298..efbb721054 100644 --- a/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java +++ b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,12 +57,11 @@ import javax.swing.*; /** - * SettingsDialog 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. + * `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 @@ -71,14 +70,33 @@ */ public final class AWTSettingsDialog extends JFrame { - public static interface SelectionListener { - - public void onSelection(int selection); + /** + * 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; - public static final int NO_SELECTION = 0, APPROVE_SELECTION = 1, CANCEL_SELECTION = 2; + + /** + * 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"); @@ -86,8 +104,12 @@ public static interface SelectionListener { // the instance being configured private final AppSettings source; - // Title Image + /** + * 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[] { @@ -114,10 +136,24 @@ public static interface SelectionListener { 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); @@ -127,10 +163,30 @@ public static boolean showDialog(AppSettings sourceSettings, boolean loadSetting 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"); @@ -166,46 +222,47 @@ public void onSelection(int selection) { 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 } } } - sourceSettings.copyFrom(settings); + // If approved, copy the modified settings back to the original source + if (result.get() == APPROVE_SELECTION) { + sourceSettings.copyFrom(settings); + } - return result.get() == AWTSettingsDialog.APPROVE_SELECTION; + return result.get() == APPROVE_SELECTION; } /** - * Instantiate a SettingsDialog for the primary display. + * Constructs a `SettingsDialog` for the primary display. * - * @param source - * the AppSettings (not null) - * @param imageFile - * the image file to use as the title of the dialog; - * null will result in to image being displayed - * @param loadSettings - * if true, copy the settings, otherwise merge them - * @throws IllegalArgumentException - * if the source is null + * @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); } /** - * /** Instantiate a SettingsDialog for the primary display. + * Constructs a `SettingsDialog` for the primary display. * - * @param source - * the AppSettings object (not null) - * @param imageFile - * the image file to use as the title of the dialog; - * null will result in to image being displayed - * @param loadSettings - * if true, copy the settings, otherwise merge them - * @throws IllegalArgumentException - * if the source is null + * @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) { @@ -232,7 +289,10 @@ protected AWTSettingsDialog(AppSettings source, URL imageFile, boolean loadSetti 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); } @@ -355,8 +415,6 @@ public void showDialog() { * init creates the components to use the dialog. */ private void createUI() { - GridBagConstraints gbc; - JPanel mainPanel = new JPanel(new GridBagLayout()); addWindowListener(new WindowAdapter() { @@ -368,8 +426,9 @@ public void windowClosing(WindowEvent e) { } }); - if (source.getIcons() != null) { - safeSetIconImages(Arrays.asList((BufferedImage[]) source.getIcons())); + Object[] sourceIcons = source.getIcons(); + if (sourceIcons != null && sourceIcons.length > 0) { + safeSetIconImages(Arrays.asList((BufferedImage[]) sourceIcons)); } setTitle(MessageFormat.format(resourceBundle.getString("frame.title"), source.getTitle())); @@ -419,7 +478,7 @@ public void actionPerformed(ActionEvent e) { gammaBox = new JCheckBox(resourceBundle.getString("checkbox.gamma")); gammaBox.setSelected(source.isGammaCorrection()); - gbc = new GridBagConstraints(); + GridBagConstraints gbc = new GridBagConstraints(); gbc.weightx = 0.5; gbc.gridx = 0; gbc.gridwidth = 2; @@ -493,7 +552,6 @@ public void actionPerformed(ActionEvent e) { // Set the button action listeners. Cancel disposes without saving, OK // saves. ok.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { if (verifyAndSaveCurrentSelection()) { @@ -501,12 +559,13 @@ public void actionPerformed(ActionEvent e) { dispose(); // System.gc() should be called to prevent "X Error of - // failed request: RenderBadPicture (invalid Picture - // parameter)" + // 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 + + // intentional double call. see this discussion: + // https://hub.jmonkeyengine.org/t/experimenting-lwjgl3/37275/12 System.gc(); System.gc(); } @@ -514,7 +573,6 @@ public void actionPerformed(ActionEvent e) { }); cancel.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { setUserSelection(CANCEL_SELECTION); @@ -568,7 +626,6 @@ public void run() { colorDepthCombo.setSelectedItem(source.getBitsPerPixel() + " bpp"); } }); - } /* @@ -577,10 +634,8 @@ public void run() { */ private void safeSetIconImages(List icons) { try { - // Due to Java bug 6445278, we try to set icon on our shared owner - // frame first. - // Otherwise, our alt-tab icon will be the Java default under - // Windows. + // Due to Java bug 6445278, we try to set icon on our shared owner frame first. + // Otherwise, our alt-tab icon will be the Java default under Windows. Window owner = getOwner(); if (owner != null) { Method setIconImages = owner.getClass().getMethod("setIconImages", List.class); @@ -608,9 +663,9 @@ private boolean verifyAndSaveCurrentSelection() { 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[] parts = display.split(" x "); + int width = Integer.parseInt(parts[0]); + int height = Integer.parseInt(parts[1]); String depthString = (String) colorDepthCombo.getSelectedItem(); int depth = -1; @@ -639,21 +694,20 @@ private boolean verifyAndSaveCurrentSelection() { } // 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; +// 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) { - valid = true; - } else { + if (fullscreen) { GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); valid = device.isFullScreenSupported(); } @@ -673,7 +727,10 @@ private boolean verifyAndSaveCurrentSelection() { 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); } @@ -769,7 +826,9 @@ private void updateResolutionChoices() { private void updateAntialiasChoices() { // maybe in the future will add support for determining this info // through PBuffer - String[] choices = new String[] { resourceBundle.getString("antialias.disabled"), "2x", "4x", "6x", "8x", "16x" }; + String[] choices = new String[] { + resourceBundle.getString("antialias.disabled"), "2x", "4x", "6x", "8x", "16x" + }; antialiasCombo.setModel(new DefaultComboBoxModel<>(choices)); antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples() / 2, 5)]); } @@ -792,6 +851,12 @@ private static URL getURL(String file) { 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); } @@ -852,7 +917,7 @@ private String[] getWindowedResolutions(DisplayMode[] modes) { * Returns every possible bit depth for the given resolution. */ private static String[] getDepths(String resolution, DisplayMode[] modes) { - List depths = new ArrayList<>(4); + 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) { @@ -865,12 +930,8 @@ private static String[] getDepths(String resolution, DisplayMode[] modes) { continue; } String res = mode.getWidth() + " x " + mode.getHeight(); - if (!res.equals(resolution)) { - continue; - } - String depth = bitDepth + " bpp"; - if (!depths.contains(depth)) { - depths.add(depth); + if (res.equals(resolution)) { + depths.add(bitDepth + " bpp"); } } @@ -884,10 +945,15 @@ private static String[] getDepths(String resolution, DisplayMode[] modes) { } /** - * Returns every possible refresh rate for the given resolution. + * 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) { - List freqs = new ArrayList<>(4); + Set freqs = new LinkedHashSet<>(4); // Use LinkedHashSet for uniqueness and order for (DisplayMode mode : modes) { String res = mode.getWidth() + " x " + mode.getHeight(); String freq; @@ -896,20 +962,19 @@ private static String[] getFrequencies(String resolution, DisplayMode[] modes) { } else { freq = mode.getRefreshRate() + " Hz"; } - if (res.equals(resolution) && !freqs.contains(freq)) { - freqs.add(freq); - } + freqs.add(freq); } return freqs.toArray(new String[0]); } /** - * Chooses the closest frequency to 60 Hz. - * - * @param resolution - * @param modes - * @return + * 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; diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java index d06b0816cc..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,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,6 +52,7 @@ * @author Nehon */ public class AnimComposer extends AbstractControl { + /** * The name of the default layer. */ @@ -66,7 +67,7 @@ public class AnimComposer extends AbstractControl { * Instantiate a composer with a single layer, no actions, and no clips. */ public AnimComposer() { - layers.put(DEFAULT_LAYER, new AnimLayer(this, DEFAULT_LAYER, null)); + layers.put(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null)); } /** @@ -121,7 +122,7 @@ public void removeAnimClip(AnimClip anim) { * @return The action corresponding to the given name. */ public Action setCurrentAction(String name) { - return setCurrentAction(name, DEFAULT_LAYER); + return setCurrentAction(name, DEFAULT_LAYER, true); } /** @@ -144,9 +145,9 @@ public Action setCurrentAction(String actionName, String layerName) { * @return The action corresponding to the given name. */ public Action setCurrentAction(String actionName, String layerName, boolean loop) { - AnimLayer l = getLayer(layerName); + AnimLayer layer = getLayer(layerName); Action currentAction = action(actionName); - l.setCurrentAction(actionName, currentAction, loop); + layer.setCurrentAction(actionName, currentAction, loop); return currentAction; } @@ -239,7 +240,8 @@ public void setTime(String layerName, double 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) { @@ -254,7 +256,8 @@ public Action action(String name) { /** * * @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) { return actions.get(name); @@ -314,7 +317,7 @@ public Action removeAction(String name) { * @param mask the desired mask for the new layer (alias created) */ public void makeLayer(String name, AnimationMask mask) { - AnimLayer l = new AnimLayer(this, name, mask); + AnimLayer l = new AnimLayer(name, mask); layers.put(name, l); } @@ -328,8 +331,8 @@ public void removeLayer(String name) { } /** - * Creates an action that will interpolate over an entire sequence - * of tweens in order. + * 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 @@ -371,8 +374,9 @@ public void reset() { } /** - * 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 */ @@ -399,7 +403,7 @@ public Set getAnimClipsNames() { @Override protected void controlUpdate(float tpf) { for (AnimLayer layer : layers.values()) { - layer.update(tpf); + layer.update(tpf, globalSpeed); } } @@ -523,9 +527,8 @@ public void cloneFields(Cloner cloner, Object original) { for (String key : layers.keySet()) { newLayers.put(key, cloner.clone(layers.get(key))); } - + newLayers.putIfAbsent(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null)); layers = newLayers; - } /** @@ -542,6 +545,8 @@ public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); animClipMap = (Map) ic.readStringSavableMap("animClipMap", new HashMap()); globalSpeed = ic.readFloat("globalSpeed", 1f); + layers = (Map) ic.readStringSavableMap("layers", new HashMap()); + layers.putIfAbsent(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null)); } /** @@ -557,5 +562,6 @@ public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap()); oc.write(globalSpeed, "globalSpeed", 1f); + oc.writeStringSavableMap(layers, "layers", new HashMap()); } } diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java index 0c3c97e998..6cd79e731d 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java @@ -32,8 +32,14 @@ package com.jme3.anim; import com.jme3.anim.tween.action.Action; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; +import java.io.IOException; /** * A named portion of an AnimComposer that can run (at most) one Action at a @@ -48,7 +54,7 @@ *

Animation time may advance at a different rate from application time, * based on speedup factors in the composer and the current Action. */ -public class AnimLayer implements JmeCloneable { +public class AnimLayer implements JmeCloneable, Savable { /** * The Action currently running on this layer, or null if none. */ @@ -57,16 +63,12 @@ public class AnimLayer implements JmeCloneable { * The name of Action currently running on this layer, or null if none. */ private String currentActionName; - /** - * The composer that owns this layer. Were it not for cloning, this field - * would be final. - */ - private AnimComposer composer; + /** * Limits the portion of the model animated by this layer. If null, this * layer can animate the entire model. */ - private final AnimationMask mask; + private AnimationMask mask; /** * The current animation time, in scaled seconds. Always non-negative. */ @@ -79,23 +81,26 @@ public class AnimLayer implements JmeCloneable { /** * The name of this layer. */ - final private String name; + 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 composer the owner (not null, alias created) * @param name the layer name (not null) * @param mask the AnimationMask (alias created) or null to allow this layer * to animate the entire model */ - AnimLayer(AnimComposer composer, String name, AnimationMask mask) { - assert composer != null; - this.composer = composer; - + AnimLayer(String name, AnimationMask mask) { assert name != null; this.name = name; @@ -248,14 +253,15 @@ public void setLooping(boolean loop) { * * @param appDeltaTimeInSeconds the amount application time to advance the * current Action, in seconds + * @param globalSpeed the global speed applied to all layers. */ - void update(float appDeltaTimeInSeconds) { + void update(float appDeltaTimeInSeconds, float globalSpeed) { Action action = currentAction; if (action == null) { return; } - double speedup = action.getSpeed() * composer.getGlobalSpeed(); + double speedup = action.getSpeed() * globalSpeed; double scaledDeltaTime = speedup * appDeltaTimeInSeconds; time += scaledDeltaTime; @@ -292,7 +298,6 @@ void update(float appDeltaTimeInSeconds) { */ @Override public void cloneFields(Cloner cloner, Object original) { - composer = cloner.clone(composer); currentAction = null; currentActionName = null; } @@ -306,4 +311,20 @@ public Object jmeClone() { 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/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java index 5058fcec24..197242cfa1 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Armature.java +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -111,7 +111,7 @@ private void createSkinningMatrices() { /** * Sets the JointModelTransform implementation - * Default is {@link MatrixJointModelTransform} + * Default is {@link SeparateJointModelTransform} * * @param modelTransformClass which implementation to use * @see JointModelTransform 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 3aa2342f28..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,13 +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.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; /** * An AnimationMask to select joints from a single Armature. */ -public class ArmatureMask implements AnimationMask { +public class ArmatureMask implements AnimationMask, Savable { - final private BitSet affectedJoints = new BitSet(); + private BitSet affectedJoints = new BitSet(); /** * Instantiate a mask that affects no joints. @@ -49,6 +86,9 @@ private BitSet getAffectedJoints() { * @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) { @@ -72,6 +112,9 @@ public boolean contains(Object target) { * @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(); @@ -85,13 +128,13 @@ public static ArmatureMask createMask(Armature armature, String fromJoint) { * @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; } @@ -100,6 +143,9 @@ public static ArmatureMask createMask(Armature armature, String... joints) { * * @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) { @@ -121,6 +167,9 @@ private Joint findJoint(Armature armature, String jointName) { * * @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); @@ -163,4 +212,16 @@ public ArmatureMask removeAncestors(Joint start) { return this; } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(affectedJoints, "affectedJoints", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + affectedJoints = ic.readBitSet("affectedJoints", null); + } } 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 a87fbc0f74..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,21 +36,36 @@ 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 { - final private Matrix4f modelTransformMatrix = new Matrix4f(); - final 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 @@ -31,7 +77,8 @@ 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); } 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 ee767d2885..92ff43514a 100644 --- a/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java +++ b/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java @@ -228,6 +228,15 @@ public void getDataAtTime(double t, float[] store) { 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. * 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 38505f9317..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-2021 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 an 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,22 +187,34 @@ 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; } /** @@ -190,7 +227,7 @@ private boolean testHardwareSupported(RenderManager rm) { * @see #isHardwareSkinningUsed() */ public void setHardwareSkinningPreferred(boolean preferred) { - hwSkinningDesired = preferred; + hwSkinningPreferred = preferred; } /** @@ -199,7 +236,7 @@ public void setHardwareSkinningPreferred(boolean preferred) { * @see #setHardwareSkinningPreferred(boolean) */ public boolean isHardwareSkinningPreferred() { - return hwSkinningDesired; + return hwSkinningPreferred; } /** @@ -209,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); } } } @@ -236,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; } @@ -304,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(); @@ -327,15 +372,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(); + } //resetting bind tangents if there is a bind tangent buffer VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent); @@ -348,9 +398,7 @@ void resetToBind() { tb.put(btb).clear(); } - pb.put(bpb).clear(); - nb.put(bnb).clear(); } } } @@ -375,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()]; @@ -434,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 matrices 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(); @@ -552,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(); @@ -583,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(); @@ -601,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; @@ -615,7 +659,9 @@ 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; @@ -688,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); } @@ -697,7 +745,9 @@ 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); } @@ -713,9 +763,6 @@ 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); } /** @@ -731,15 +778,13 @@ public void read(JmeImporter im) throws IOException { 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); } /** @@ -747,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 3492d17eec..39b66d5d36 100644 --- a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java +++ b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java @@ -301,6 +301,15 @@ 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. * 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 9dd961b530..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 @@ -57,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/action/Action.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java index 0fb423f336..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,14 +84,19 @@ 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; } /** - * Alter the length (duration) of this Action. This can be used to extend + * 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) @@ -40,44 +105,66 @@ 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) */ @@ -105,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 99c559fca6..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,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,14 +35,40 @@ 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 { 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()); @@ -52,33 +78,35 @@ public BaseAction(Tween tween) { subActions.toArray(actions); } - private void gatherActions(Tween tween, List subActions) { - if (tween instanceof Action) { - subActions.add((Action) tween); - } else if (tween instanceof ContainsTweens) { - Tween[] tweens = ((ContainsTweens) tween).getTweens(); - for (Tween t : tweens) { - gatherActions(t, subActions); - } - } - } - /** - * @return true if mask propagation to child actions is enabled else returns false + * 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) + * 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); @@ -94,4 +122,21 @@ public void setMask(AnimationMask mask) { 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); + } else if (tween instanceof ContainsTweens) { + Tween[] tweens = ((ContainsTweens) tween).getTweens(); + for (Tween t : tweens) { + gatherActions(t, subActions); + } + } + } } 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 c9882529f9..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 @@ -105,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); } 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 9f5c855521..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,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.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(); @@ -59,8 +96,13 @@ 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 @@ -100,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/util/AnimMigrationUtils.java b/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java index 3fc65f63a5..6c463f329d 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.util; import com.jme3.anim.*; @@ -10,8 +41,8 @@ public class AnimMigrationUtils { - final private static AnimControlVisitor animControlVisitor = new AnimControlVisitor(); - final private static SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor(); + private static final AnimControlVisitor animControlVisitor = new AnimControlVisitor(); + private static final SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor(); /** * A private constructor to inhibit instantiation of this class. @@ -64,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 837c50ca2e..708d1410dd 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.util; import com.jme3.anim.Joint; diff --git a/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java b/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java index 56fbbf4788..4e04c49cbb 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.util; import java.util.Collections; diff --git a/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java index f771d44edd..1a2a690d1b 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.util; import com.jme3.anim.tween.action.Action; diff --git a/jme3-core/src/main/java/com/jme3/animation/LoopMode.java b/jme3-core/src/main/java/com/jme3/animation/LoopMode.java index 9572b87bc5..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-2021 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 @@ -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/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index d6e7392cae..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -250,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(); @@ -321,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,7 +350,6 @@ void resetToBind() { } pb.put(bpb).clear(); - nb.put(bnb).clear(); } } } @@ -574,8 +580,10 @@ 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(); @@ -603,7 +611,9 @@ 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; @@ -676,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); } @@ -685,7 +697,9 @@ 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); } 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 59e96c4046..a7122765a5 100644 --- a/jme3-core/src/main/java/com/jme3/app/AppTask.java +++ b/jme3-core/src/main/java/com/jme3/app/AppTask.java @@ -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(); @@ -100,7 +106,8 @@ public V get() throws InterruptedException, ExecutionException { } @Override - public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + public V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { stateLock.lock(); try { if (!isDone()) { 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 198d730efa..dba60e140c 100644 --- a/jme3-core/src/main/java/com/jme3/app/Application.java +++ b/jme3-core/src/main/java/com/jme3/app/Application.java @@ -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; @@ -226,7 +228,7 @@ public interface Application { * After the application has stopped, it cannot be used anymore. * @param waitFor true→wait for the context to be fully destroyed, - * true→don't wait + * false→don't wait */ public void stop(boolean waitFor); 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 e7ae0a7884..1c95730bbc 100644 --- a/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java +++ b/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java @@ -32,7 +32,11 @@ 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; 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 d7b56511a6..68ee1483fb 100644 --- a/jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java +++ b/jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java @@ -122,53 +122,52 @@ protected void refreshBackground() { 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 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 f4beacdf85..9e3740bec9 100644 --- a/jme3-core/src/main/java/com/jme3/app/ChaseCameraAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/ChaseCameraAppState.java @@ -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; @@ -209,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); 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 61919d4f86..143e13210a 100644 --- a/jme3-core/src/main/java/com/jme3/app/DebugKeysAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/DebugKeysAppState.java @@ -55,7 +55,7 @@ public class DebugKeysAppState extends AbstractAppState { public static final String INPUT_MAPPING_MEMORY = "SIMPLEAPP_Memory"; private Application app; - final 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); } @@ -111,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 13700a870a..2936a30072 100644 --- a/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java +++ b/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java @@ -43,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; @@ -59,10 +59,10 @@ public class DetailedProfiler implements AppProfiler { private String curSpPath = null; private VpStep lastVpStep = null; - final private StringBuilder path = new StringBuilder(256); - final private StringBuilder vpPath = new StringBuilder(256); + private final StringBuilder path = new StringBuilder(256); + private final StringBuilder vpPath = new StringBuilder(256); - final private Deque idsPool = new ArrayDeque<>(100); + private final Deque idsPool = new ArrayDeque<>(100); StatLine frameTime; @@ -152,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(); @@ -185,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; } @@ -256,8 +259,8 @@ private int getUnusedTaskId() { } public static class StatLine { - final private long[] cpuTimes = new long[MAX_FRAMES]; - final 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; 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 1aa244a797..7a3e0393cc 100644 --- a/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java +++ b/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java @@ -60,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"; - final private DetailedProfiler prof = new DetailedProfiler(); + private final DetailedProfiler prof = new DetailedProfiler(); private float time = 0; private BitmapFont font; private BitmapFont bigFont; - final private Node ui = new Node("Stats ui"); - final 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; @@ -83,14 +83,14 @@ public class DetailedProfilerState extends BaseAppState { private StatLineView rootLine; private int height = 0; - final private DecimalFormat df = new DecimalFormat("##0.00", new DecimalFormatSymbols(Locale.US)); + private final DecimalFormat df = new DecimalFormat("##0.00", new DecimalFormatSymbols(Locale.US)); - final private ColorRGBA dimmedWhite = ColorRGBA.White.mult(0.7f); - final private ColorRGBA dimmedGreen = ColorRGBA.Green.mult(0.7f); - final private ColorRGBA dimmedOrange = ColorRGBA.Orange.mult(0.7f); - final 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); - final private ProfilerInputListener inputListener = new ProfilerInputListener(); + private final ProfilerInputListener inputListener = new ProfilerInputListener(); public DetailedProfilerState() { @@ -101,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); @@ -116,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); @@ -221,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) { @@ -285,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; @@ -413,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); @@ -466,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/LegacyApplication.java b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java index 626dec4bfd..1e0a75daf7 100644 --- a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java @@ -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; @@ -188,9 +199,9 @@ 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."); + if (this.assetManager != null) { + throw new IllegalStateException("Can only set asset manager" + " before initialization."); + } this.assetManager = assetManager; } @@ -204,11 +215,16 @@ private void initAssetManager() { 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; } } @@ -332,21 +348,25 @@ private void initCamera() { */ 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.useJoysticks()) { joyInput = context.getJoyInput(); - if (joyInput != null) + if (joyInput != null) { joyInput.initialize(); + } } inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput); @@ -578,9 +598,8 @@ public void reshape(int w, int h) { } } - @Override - public void rescale(float x, float y){ + public void rescale(float x, float y) { if (renderManager != null) { renderManager.notifyRescale(x, y); } @@ -618,7 +637,7 @@ public void stop() { * After the application has stopped, it cannot be used anymore. * * @param waitFor true→wait for the context to be fully destroyed, - * true→don't wait + * false→don't wait */ @Override public void stop(boolean waitFor) { @@ -651,9 +670,8 @@ public void initialize() { initAudio(); // update timer so that the next delta is not too large -// timer.update(); + // timer.update(); timer.reset(); - // user code here } @@ -667,8 +685,12 @@ public void handleError(String errMsg, Throwable t) { // Display error message on screen if not in headless mode if (context.getType() != JmeContext.Type.Headless) { if (t != null) { - JmeSystem.handleErrorMessage(errMsg + "\n" + t.getClass().getSimpleName() - + (t.getMessage() != null ? ": " + t.getMessage() : "")); + JmeSystem.handleErrorMessage( + errMsg + + "\n" + + t.getClass().getSimpleName() + + (t.getMessage() != null ? ": " + t.getMessage() : "") + ); } else { JmeSystem.handleErrorMessage(errMsg); } @@ -770,42 +792,49 @@ public void update() { // Make sure the audio renderer is available to callables AudioContext.setAudioRenderer(audioRenderer); - if (prof != null) + if (prof != null) { prof.appStep(AppStep.QueuedTasks); + } runQueuedTasks(); - if (speed == 0 || paused) + if (speed == 0 || paused) { return; + } timer.update(); if (inputEnabled) { - if (prof != null) + if (prof != null) { prof.appStep(AppStep.ProcessInput); + } inputManager.update(timer.getTimePerFrame()); } if (audioRenderer != null) { - if (prof != null) + if (prof != null) { prof.appStep(AppStep.ProcessAudio); + } audioRenderer.update(timer.getTimePerFrame()); } - // user code here } protected void destroyInput() { - if (mouseInput != null) + 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; } @@ -819,8 +848,9 @@ public void destroy() { stateManager.cleanup(); destroyInput(); - if (audioRenderer != null) + if (audioRenderer != null) { audioRenderer.cleanup(); + } timer.reset(); } @@ -840,6 +870,7 @@ public ViewPort getViewPort() { } private class RunnableWrapper implements Callable { + private final Runnable runnable; public RunnableWrapper(Runnable runnable) { @@ -852,4 +883,26 @@ public Object call() { 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/SimpleApplication.java b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java index 90e1bb33e0..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-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,26 +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; @@ -76,31 +97,48 @@ public abstract class SimpleApplication extends LegacyApplication { protected BitmapFont guiFont; protected FlyByCamera flyCam; protected boolean showSettings = true; - final private AppActionListener actionListener = new AppActionListener(); + private final AppActionListener actionListener = new AppActionListener(); private class AppActionListener implements ActionListener { @Override - public void onAction(String name, boolean value, float tpf) { - if (!value) { + 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(); + 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(), + this(new StatsAppState(), + new FlyCamAppState(), + new AudioListenerState(), + new DebugKeysAppState(), new ConstantVerifierState()); } + /** + * 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); } @@ -111,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; } @@ -127,57 +166,73 @@ public void start() { } /** - * Returns the application's 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's 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; @@ -197,109 +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 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) + if (prof != null) { prof.appStep(AppStep.BeginFrame); + } - super.update(); // makes sure to execute AppTasks + // Executes AppTasks from the main thread + super.update(); + + // Skip updates if paused or speed is zero if (speed == 0 || paused) { return; } float tpf = timer.getTimePerFrame() * speed; - // update states - if (prof != null) + // 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) + // 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) + // Render AppStates and the scene + if (prof != null) { prof.appStep(AppStep.StateManagerRender); + } stateManager.render(renderManager); - if (prof != null) + 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) + 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/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java index f6b28fb642..8e274e97b5 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsView.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java @@ -60,11 +60,11 @@ * */ public class StatsView extends Node implements Control, JmeCloneable { - final private BitmapText statText; - final private Statistics statistics; + private final BitmapText statText; + private final Statistics statistics; - final private String[] statLabels; - final private int[] statData; + private final String[] statLabels; + private final int[] statData; private boolean enabled = true; @@ -96,8 +96,9 @@ public float getHeight() { @Override public void update(float tpf) { - if (!isEnabled()) + if (!isEnabled()) { return; + } statistics.getData(statData); stringBuilder.setLength(0); 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 956818e3d5..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-2021 jMonkeyEngine + * Copyright (c) 2014-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,30 +29,39 @@ * NEGLIGENCE OR 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.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 { - private 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 @@ -73,7 +82,14 @@ public class ConstantVerifierState extends BaseAppState { 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)), @@ -91,24 +107,34 @@ public class ConstantVerifierState extends BaseAppState { 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 + } - final 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); } /** - * 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 + * @param errorType The mechanism to use when a constant's value drifts. */ public ConstantVerifierState(ErrorType errorType) { this(errorType, DEFAULT_CHECKS); @@ -126,14 +152,32 @@ private ConstantVerifierState(ErrorType errorType, Checker... checkers) { this.checkers.addAll(Arrays.asList(checkers)); } + /** + * 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)); } + /** + * 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; } @@ -161,21 +205,26 @@ public void postRender() { 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: + 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()); + throw new RuntimeException("JME Constant has changed, " + checker.toString()); } break; case Log: if (!checker.isValid()) { - log.severe("Constant has changed, " + checker.toString()); + log.severe("JME Constant has changed, " + checker.toString()); } break; } @@ -188,8 +237,9 @@ protected void checkValues() { * mean anything. */ private static class Checker { - private Object constant; - private Object goodValue; + + private final Object constant; + private final Object goodValue; public Checker(Object constant, Object goodValue) { if (constant == null) { @@ -197,7 +247,7 @@ public Checker(Object constant, Object goodValue) { } if (!constant.equals(goodValue)) { throw new IllegalArgumentException( - "Constant value:" + constant + " does not match value:" + goodValue); + "Constant value: " + constant + " does not match value: " + goodValue); } this.constant = constant; this.goodValue = goodValue; 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 be2c834ea9..3307cca4cb 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java @@ -39,6 +39,8 @@ 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. * @@ -101,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 { 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 cde85ecec1..73762a1234 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java @@ -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,23 +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. diff --git a/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.java b/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.java index 74a00dae42..b428fd08d1 100644 --- a/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.java +++ b/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -65,13 +65,14 @@ public interface CloneableSmartAsset extends Cloneable { public CloneableSmartAsset clone(); /** - * Set by the {@link AssetManager} to track this asset. + * Assigns the specified AssetKey to the asset. * - * Only clones of the asset has this set, the original copy that - * was loaded has this key set to null so that only the clones are tracked - * for garbage collection. + * This is invoked by the {@link AssetManager}. + * Only clones of the asset have non-null keys. The original copy that + * was loaded has no key assigned. Only the clones are tracked + * for garbage collection. * - * @param key The AssetKey to set + * @param key The AssetKey to assign */ public void setKey(AssetKey key); diff --git a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java index c81e56fec5..a8ff73f2c4 100644 --- a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java @@ -73,8 +73,8 @@ public class DesktopAssetManager implements AssetManager { final private CopyOnWriteArrayList eventListeners = new CopyOnWriteArrayList<>(); - final private List classLoaders = - Collections.synchronizedList(new ArrayList<>()); + @Deprecated + final private List classLoaders = Collections.synchronizedList(new ArrayList<>()); public DesktopAssetManager() { this(null); @@ -99,21 +99,23 @@ private void loadConfigFile(URL configFile) { } } + @Deprecated @Override public void addClassLoader(ClassLoader loader) { classLoaders.add(loader); } + @Deprecated @Override public void removeClassLoader(ClassLoader loader) { classLoaders.remove(loader); } + @Deprecated @Override public List getClassLoaders() { return Collections.unmodifiableList(classLoaders); } - @Override public void addAssetEventListener(AssetEventListener listener) { eventListeners.add(listener); diff --git a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java index 1b6275d12d..90af79d803 100644 --- a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java +++ b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java @@ -124,10 +124,7 @@ protected T initialValue() { } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) { - logger.log(Level.SEVERE, "Cannot create locator of type {0}, does" - + " the class have an empty and publicly accessible" - + " constructor?", type.getName()); - logger.throwing(type.getName(), "", ex); + logger.log(Level.SEVERE, "An exception occurred while instantiating asset locator: " + type.getName(), ex); } return null; } diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java index 5e0a90c173..722afde411 100644 --- a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java +++ b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java @@ -60,8 +60,7 @@ public class WeakRefCloneAssetCache implements AssetCache { * Maps cloned key to AssetRef which has a weak ref to the original * key and a strong ref to the original asset. */ - private final ConcurrentHashMap smartCache - = new ConcurrentHashMap<>(); + private final ConcurrentHashMap smartCache = new ConcurrentHashMap<>(); /** * Stored in the ReferenceQueue to find out when originalKey is collected diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java b/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java index 80f5f63040..249d9601eb 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -132,6 +132,6 @@ public NativeObject createDestructableClone() { @Override public long getUniqueId() { - return ((long) OBJTYPE_AUDIOBUFFER << 32) | ((long) id); + return ((long) OBJTYPE_AUDIOBUFFER << 32) | (0xffffffffL & (long) id); } } diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index 67ac7cba2f..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -317,6 +317,10 @@ public void setAudioData(AudioData audioData, AudioKey audioKey) { data = audioData; this.audioKey = audioKey; } + + public AudioKey getAudioKey() { + return audioKey; + } /** * @return The {@link AudioData} set previously with diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java index ccc74e181c..15b40c5b43 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -222,6 +222,6 @@ public void setTime(float time) { @Override public long getUniqueId() { - return ((long) OBJTYPE_AUDIOSTREAM << 32) | ((long) ids[0]); + return ((long) OBJTYPE_AUDIOSTREAM << 32) | (0xffffffffL & (long) ids[0]); } } diff --git a/jme3-core/src/main/java/com/jme3/audio/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 b974ee25d3..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); - } - + /** 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); } + /** + * 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 reflectGain, - float reflectDelay, 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; @@ -108,6 +154,16 @@ public Environment(float density, float diffusion, float gain, float gainHf, this.reflectGain = reflectGain; } + /** + * 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 83bc99e753..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-2020 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,9 +35,12 @@ 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() { super(); @@ -49,12 +52,28 @@ protected Filter(int 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 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 5ef28b4f09..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; + /** + * Constructs a new {@code Listener} with default parameters. + */ public Listener() { location = new Vector3f(); velocity = new Vector3f(); rotation = new Quaternion(); } + /** + * 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) { - location = source.location.clone(); - velocity = source.velocity.clone(); - rotation = source.rotation.clone(); - volume = source.volume; + 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 } + /** + * 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; } + /** + * 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); } + /** + * 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); } + /** + * 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 aaf1f5b69b..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-2020 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); } + /** + * 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,10 +109,21 @@ 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"); @@ -80,25 +136,35 @@ public void setVolume(float volume) { 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 { 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/ALAudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java index 834d80eca1..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-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,19 +31,36 @@ */ 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; +/** + * 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()); @@ -55,79 +72,97 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { // which is exactly 1 second of audio. private static final int BUFFER_SIZE = 35280; private static final int STREAMING_BUFFER_COUNT = 5; - private final static int MAX_NUM_CHANNELS = 64; - private 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[] channelSources; - private int nextChan = 0; - private final ArrayList freeChannels = 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 boolean supportDisconnect = 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; + // 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); - channelSources = 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}", + + " * 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), @@ -137,58 +172,82 @@ 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); + } } - // Disconnected audio devices (such as USB sound cards, headphones...) - // never reconnect, the whole context must be re-created - supportDisconnect = alc.alcIsExtensionPresent("ALC_EXT_disconnect"); + 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 + // Stops channels and detaches buffers/filters for (int i = 0; i < channelSources.length; i++) { if (channelSources[i] != null) { clearChannel(i); @@ -201,24 +260,39 @@ 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()) { @@ -228,6 +302,11 @@ public void initialize() { // 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); @@ -235,20 +314,28 @@ 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; } @@ -260,40 +347,52 @@ public void run() { 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); @@ -302,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(); @@ -322,38 +440,27 @@ public float getSourcePlaybackTime(AudioSource src) { 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: @@ -362,10 +469,20 @@ public float getSourcePlaybackTime(AudioSource src) { * 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(); @@ -374,228 +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 the 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; + + case ReverbFilter: + if (src.isPositional()) { + applySourceReverbFilter(sourceId, src); } - Filter dryFilter = src.getDryFilter(); - int filterId; - if (dryFilter == null) { - filterId = EFX.AL_FILTER_NULL; - } else { - if (dryFilter.isUpdateNeeded()) { - updateFilter(dryFilter); + break; + + 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); } - filterId = dryFilter.getId(); } - // NOTE: must re-attach filter for changes to apply. - al.alSourcei(id, EFX.AL_DIRECT_FILTER, filterId); 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; @@ -603,63 +729,84 @@ 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 (freeChannels.size() > 0) { - return freeChannels.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 { 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(); @@ -668,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()); @@ -681,38 +829,60 @@ 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); @@ -721,7 +891,7 @@ private boolean fillStreamingSource(int sourceId, AudioStream stream, boolean lo 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); @@ -730,23 +900,23 @@ private boolean fillStreamingSource(int sourceId, AudioStream stream, boolean lo // 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) { + 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. @@ -757,6 +927,7 @@ private boolean fillStreamingSource(int sourceId, AudioStream stream, boolean lo } } + // Update the stream's internal counter for processed bytes stream.setUnqueuedBufferBytes(stream.getUnqueuedBufferBytes() + unqueuedBufferBytes); return success; @@ -765,29 +936,30 @@ 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 at the 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) { + 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; @@ -800,9 +972,8 @@ private void attachStreamToSource(int sourceId, AudioStream stream, boolean loop } } - 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) { @@ -815,6 +986,12 @@ 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 (channelSources[index] != null) { @@ -826,13 +1003,14 @@ private void clearChannel(int index) { // 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 = 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); } } @@ -841,8 +1019,8 @@ private void clearChannel(int index) { } } - 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; @@ -851,7 +1029,7 @@ 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); } } @@ -862,29 +1040,71 @@ public void update(float tpf) { } } + /** + * Checks the device connection status and attempts to restart the renderer if disconnected. + * Called periodically from the decoder thread. + */ private void checkDevice() { - - // If the device is disconnected, pick a new one - if (isDisconnected()) { - logger.log(Level.INFO, "Current audio device disconnected."); + if (isDeviceDisconnected()) { + logger.log(Level.WARNING, "Audio device disconnected! Attempting to restart audio renderer..."); restartAudioRenderer(); } } - private boolean isDisconnected() { - if (!supportDisconnect) { + /** + * 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; @@ -894,80 +1114,104 @@ public void updateInRenderThread(float tpf) { 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; @@ -976,6 +1220,7 @@ public void updateInDecoderThread(float tpf) { for (int i = 0; i < channels.length; i++) { AudioSource src = channelSources[i]; + // Only process streaming sources associated with this channel if (src == null || !(src.getAudioData() instanceof AudioStream)) { continue; } @@ -983,21 +1228,26 @@ 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 && oalStatus == Status.Stopped && jmeStatus == Status.Playing) { + // 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 occurred while playing stream"); + 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); } @@ -1010,35 +1260,60 @@ 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(); @@ -1047,29 +1322,34 @@ 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); + attachAudioToSource(sourceId, audioData, false); channelSources[index] = src; // play the channel @@ -1077,6 +1357,11 @@ public void playSourceInstance(AudioSource src) { } } + /** + * 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(); @@ -1086,36 +1371,51 @@ public void playSource(AudioSource src) { } if (src.getStatus() == Status.Playing) { + // Already playing, do nothing. return; - } else if (src.getStatus() == Status.Stopped) { - // Assertion removed because it seems it's not possible to have - // something different from -1 when first playing an AudioNode. - // assert src.getChannel() != -1; + } + + if (src.getStatus() == Status.Stopped) { - // allocate channel to this source + 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); } + // Set all source parameters and attach the audio data channelSources[index] = src; - setSourceParams(channels[index], src, false); - attachAudioToSource(channels[index], data, src.isLooping()); + 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(); @@ -1124,15 +1424,27 @@ 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) { @@ -1140,18 +1452,24 @@ public void stopSource(AudioSource src) { 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. + // 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); @@ -1163,107 +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(); } } + /** + * Deletes the OpenAL objects associated with the AudioData. + * @param audioData The AudioData to delete. + */ @Override - public void deleteAudioData(AudioData ad) { + 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/EFX.java b/jme3-core/src/main/java/com/jme3/audio/openal/EFX.java index 86170c142a..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 @@ -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/bounding/BoundingBox.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java index b99d4d7351..6b0e023b34 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ import java.io.IOException; import java.nio.FloatBuffer; //import com.jme.scene.TriMesh; +import java.util.Objects; /** * BoundingBox describes a bounding volume as an axis-aligned box. @@ -587,6 +588,76 @@ public BoundingVolume clone(BoundingVolume store) { return rVal; } + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingBox)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingBox otherBoundingBox = (BoundingBox) other; + if (Float.compare(xExtent, otherBoundingBox.xExtent) != 0) { + return false; + } else if (Float.compare(yExtent, otherBoundingBox.yExtent) != 0) { + return false; + } else if (Float.compare(zExtent, otherBoundingBox.zExtent) != 0) { + return false; + } else { + return super.equals(otherBoundingBox); + } + } + + /** + * Returns a hash code. If two bounding boxes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(xExtent, yExtent, zExtent); + hash = 59 * hash + super.hashCode(); + + return hash; + } + + /** + * Tests for approximate equality with the specified bounding box, using the + * specified tolerance. If {@code other} is null, false is returned. Either + * way, the current instance is unaffected. + * + * @param aabb the bounding box to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if all components are within tolerance, otherwise false + */ + public boolean isSimilar(BoundingBox aabb, float epsilon) { + if (aabb == null) { + return false; + } else if (Float.compare(Math.abs(aabb.xExtent - xExtent), epsilon) > 0) { + return false; + } else if (Float.compare(Math.abs(aabb.yExtent - yExtent), epsilon) > 0) { + return false; + } else if (Float.compare(Math.abs(aabb.zExtent - zExtent), epsilon) > 0) { + return false; + } else if (!center.isSimilar(aabb.getCenter(), epsilon)) { + return false; + } + // The checkPlane field is ignored. + return true; + } + /** * toString returns the string representation of this object. * The form is: "[Center: vector xExtent: X.XX yExtent: Y.YY zExtent: diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java index 5b46846a23..67cf7263f8 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,7 @@ import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; @@ -651,6 +652,68 @@ public BoundingVolume clone(BoundingVolume store) { return new BoundingSphere(radius, center.clone()); } + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingSphere)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingSphere otherBoundingSphere = (BoundingSphere) other; + if (Float.compare(radius, otherBoundingSphere.getRadius()) != 0) { + return false; + } else { + return super.equals(otherBoundingSphere); + } + } + + /** + * Returns a hash code. If two bounding boxes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(radius); + hash = 59 * hash + super.hashCode(); + + return hash; + } + + /** + * Tests for approximate equality with the specified bounding sphere, using + * the specified tolerance. If {@code other} is null, false is returned. + * Either way, the current instance is unaffected. + * + * @param sphere the bounding sphere to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if all components are within tolerance, otherwise false + */ + public boolean isSimilar(BoundingSphere sphere, float epsilon) { + if (sphere == null) { + return false; + } else if (Float.compare(Math.abs(sphere.getRadius() - radius), epsilon) > 0) { + return false; + } else if (!center.isSimilar(sphere.getCenter(), epsilon)) { + return false; + } + // The checkPlane field is ignored. + return true; + } + /** * toString returns the string representation of this object. * The form is: "Radius: RRR.SSSS Center: vector". diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java index 88cd4148ef..3a80764910 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Objects; /** * BoundingVolume defines an interface for dealing with @@ -180,6 +181,48 @@ public final BoundingVolume transform(Transform trans) { */ public abstract BoundingVolume clone(BoundingVolume store); + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingVolume)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingVolume otherBoundingVolume = (BoundingVolume) other; + if (!center.equals(otherBoundingVolume.getCenter())) { + return false; + } + // The checkPlane field is ignored. + + return true; + } + + /** + * Returns a hash code. If two bounding volumes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(center); + // The checkPlane field is ignored. + + return hash; + } + public final Vector3f getCenter() { return center; } diff --git a/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java index b03850c255..daca01b843 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java @@ -93,7 +93,7 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { private Node scene; protected TimeLine timeLine = new TimeLine(); private int lastFetchedKeyFrame = -1; - final private List cinematicEvents = new ArrayList<>(); + private final List cinematicEvents = new ArrayList<>(); private Map cameras = new HashMap<>(); private CameraNode currentCam; private boolean initialized = false; diff --git a/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java b/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java index 7a139a43a4..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-2021 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; @@ -177,6 +179,40 @@ public void read(JmeImporter im) throws IOException { 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 diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java index 34c5275d20..7d7721e166 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java @@ -53,7 +53,7 @@ */ public class AnimEvent extends AbstractCinematicEvent { - final public static Logger logger + public static final Logger logger = Logger.getLogger(AnimEvent.class.getName()); /* diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/CameraEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/CameraEvent.java index 08b2e8fe46..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,27 +40,37 @@ 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 { + /** + * 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; - } - - public void setCameraName(String cameraName) { - this.cameraName = cameraName; - } - + /** + * For serialization only. Do not use. + */ public CameraEvent() { } - public CameraEvent(Cinematic parentEvent, String cameraName) { - this.cinematic = parentEvent; + /** + * 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; } @@ -102,33 +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; } /** - * used internally for serialization + * Returns the name of the camera that this event will activate. + * @return The camera name. + */ + public String getCameraName() { + return cameraName; + } + + /** + * 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 the exporter (not null) - * @throws IOException from the exporter + * @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 the importer (not null) - * @throws IOException from the importer + * @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/MotionEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java index 2bd4485768..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-2021 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,6 +70,7 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC protected Direction directionType = Direction.None; protected MotionPath path; private boolean isControl = true; + private final Quaternion tempRotation = new Quaternion(); /** * the distance traveled by the spatial on the path */ @@ -79,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. */ @@ -229,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); } /** @@ -249,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) { @@ -260,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: @@ -272,6 +270,7 @@ private void computeTargetDirection() { } break; case None: + // no-op break; default: break; @@ -312,6 +311,9 @@ public Object jmeClone() { @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); } @@ -373,8 +375,7 @@ 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 custom up vector. + * If a custom up vector is desired, use {@link #setDirection(Vector3f, Vector3f)}. * This method is used by the motion path. * * @param direction the desired forward direction (not null, unaffected) diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java b/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java index 6f422baed3..12bf88b23a 100644 --- a/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java +++ b/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java @@ -411,15 +411,20 @@ public final int intersectWhere(Ray r, t = t_world; } - Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null); - Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o); - float worldSpaceDist = o.distance(contactPoint); - - CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist); - cr.setContactNormal(contactNormal); - cr.setTriangleIndex(tree.getTriangleIndex(i)); - results.addCollision(cr); - cols++; + // this second isInfinite test is unlikely to fail but due to numeric precision it might + // be the case that in local coordinates it just hits and in world coordinates it just misses + // this filters those cases out (treating them as misses). + if (!Float.isInfinite(t)){ + Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null); + Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o); + float worldSpaceDist = o.distance(contactPoint); + + CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist); + cr.setContactNormal(contactNormal); + cr.setTriangleIndex(tree.getTriangleIndex(i)); + results.addCollision(cr); + cols++; + } } } } 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 a2d481f36b..338894bbdf 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -1124,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(); 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 d6d2c5ecf2..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ import com.jme3.math.FastMath; import com.jme3.math.Vector3f; import com.jme3.util.clone.Cloner; + import java.io.IOException; /** @@ -101,13 +102,10 @@ public void read(JmeImporter im) throws IOException { @Override public DefaultParticleInfluencer clone() { - try { - DefaultParticleInfluencer clone = (DefaultParticleInfluencer) super.clone(); - clone.initialVelocity = initialVelocity.clone(); - return clone; - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } + // Set up the cloner for the type of cloning we want to do. + Cloner cloner = new Cloner(); + DefaultParticleInfluencer clone = cloner.clone(this); + return clone; } /** 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 a026040b20..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 NewtonianParticleInfluencer 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 1350463196..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,7 +42,7 @@ * 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. @@ -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/shapes/EmitterBoxShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java index 12f6645809..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) { @@ -108,18 +136,40 @@ public void cloneFields(Cloner cloner, Object original) { 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/EmitterMeshFaceShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java index cda0c911eb..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-2021 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,79 +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); } } /** - * Randomly selects a 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 - * storage for the coordinates of the selected point + * @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); } /** - * Randomly selects a point on a random face. - * The {@code normal} argument is set to the normal of the selected face. + * 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 - * storage for the coordinates of the selected point - * @param normal - * storage for the normal of the selected face + * @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/EmitterPointShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java index 0eca81c226..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; } @@ -80,26 +95,43 @@ 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 a9d7dabca7..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"); } @@ -92,6 +111,11 @@ 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/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/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/util/BoundingSphereDebug.java b/jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java index c5510dedde..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-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -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 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/LightsDebugState.java b/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java index e2a159bf04..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,154 +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. + * Creates a new gizmo spatial for a given light based on its type. * - * @param scene the root of the desired scene (alias created) + * @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(); } /** - * returns the scale of the probe's debug sphere - * @return the scale factor + * 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; + } + + /** + * 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 boolean isShowOnTop() { + return showOnTop; + } + + /** + * 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 float getProbeScale() { - return probeScale; + public void setShowOnTop(boolean showOnTop) { + this.showOnTop = showOnTop; + if (viewPort != null) { + viewPort.setClearDepth(showOnTop); + } } /** - * sets the scale of the probe's debug sphere + * 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 probeScale the scale factor (default=1) + * @param lightFilter A {@link Predicate} that tests a {@link Light} object. */ - public void setProbeScale(float probeScale) { - this.probeScale = probeScale; + 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/export/FormatVersion.java b/jme3-core/src/main/java/com/jme3/export/FormatVersion.java index 7de953abcb..2e34f2365b 100644 --- a/jme3-core/src/main/java/com/jme3/export/FormatVersion.java +++ b/jme3-core/src/main/java/com/jme3/export/FormatVersion.java @@ -39,9 +39,16 @@ 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. 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/SavableClassUtil.java b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java index 81887511cf..79364c96ea 100644 --- a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java +++ b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java @@ -57,7 +57,7 @@ */ public class SavableClassUtil { - private final static HashMap CLASS_REMAPPINGS = new HashMap<>(); + private static final HashMap CLASS_REMAPPINGS = new HashMap<>(); private static void addRemapping(String oldClass, Class newClass) { CLASS_REMAPPINGS.put(oldClass, newClass.getName()); @@ -201,6 +201,10 @@ public static Savable fromName(String className) } } + /** + * @deprecated use {@link #fromName(java.lang.String)} instead + */ + @Deprecated public static Savable fromName(String className, List loaders) throws InstantiationException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, IOException { diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java b/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java index 86e6a5fd8e..13b16785d7 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java @@ -43,7 +43,7 @@ public class BitmapCharacterSet implements Savable { private int renderedSize; private int width; private int height; - final private IntMap> characters; + private final IntMap> characters; private int pageSize; @Override diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapFont.java b/jme3-core/src/main/java/com/jme3/font/BitmapFont.java index 0fec21364e..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,14 +31,22 @@ */ 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 { @@ -87,33 +95,30 @@ public enum VAlign { 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; /** - * @return true, if this is a right-to-left font, otherwise it will return false. + * Creates a new instance of `BitmapFont`. + * This constructor is primarily used for deserialization. */ - public boolean isRightToLeft() { - return rightToLeft; + public BitmapFont() { } /** - * Specify if this is a right-to-left font. By default it is set to false. - * This can be "overwritten" in the BitmapText constructor. + * 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 rightToLeft true → right-to-left, false → left-to-right - * (default=false) + * @param content The initial text content for the label. + * @return A new {@link BitmapText} instance. */ - public void setRightToLeft(boolean rightToLeft) { - this.rightToLeft = rightToLeft; - } - - public BitmapFont() { - } - public BitmapText createLabel(String content) { BitmapText label = new BitmapText(this); label.setSize(getCharSet().getRenderedSize()); @@ -121,27 +126,81 @@ public BitmapText createLabel(String content) { return label; } + /** + * 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; } @@ -192,26 +251,19 @@ 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); - oc.write(rightToLeft, "rightToLeft", false); - oc.write(glyphParser, "glyphParser", 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); - rightToLeft = ic.readBoolean("rightToLeft", false); - glyphParser = (GlyphParser) ic.readSavable("glyphParser", null); - } - + /** + * 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 bases its calculation on a different @@ -252,29 +304,36 @@ public float getLineWidth(CharSequence text) { boolean firstCharOfLine = true; // float sizeScale = (float) block.getSize() / charSet.getRenderedSize(); float sizeScale = 1f; - CharSequence characters = glyphParser != null ? glyphParser.parse(text) : text; - for (int i = 0; i < characters.length(); i++) { - char theChar = characters.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(theChar); + BitmapCharacter c = charSet.getCharacter(currChar); if (c != null) { - if (theChar == '\\' && i < characters.length() - 1 && characters.charAt(i + 1) == '#') { - if (i + 5 < characters.length() && characters.charAt(i + 5) == '#') { + // Custom color tag skipping logic: + // Assumes tags are of the form `\#RRGGBB#` (9 chars total) or `\#RRGGBBAA#` (12 chars total). + if (currChar == '\\' && i < processedText.length() - 1 && processedText.charAt(i + 1) == '#') { + // Check for `\#XXXXX#` (6 chars after '\', including final '#') + if (i + 5 < processedText.length() && processedText.charAt(i + 5) == '#') { i += 5; continue; - } else if (i + 8 < characters.length() && characters.charAt(i + 8) == '#') { + } + // Check for `\#XXXXXXXX#` (9 chars after '\', including final '#') + else if (i + 8 < processedText.length() && processedText.charAt(i + 8) == '#') { i += 8; continue; } } if (!firstCharOfLine) { - lineWidth += findKerningAmount(lastChar, theChar) * sizeScale; + lineWidth += findKerningAmount(lastChar, currChar) * sizeScale; } else { if (rightToLeft) { // Ignore offset, so it will be compatible with BitmapText.getLineWidth(). @@ -292,7 +351,7 @@ public float getLineWidth(CharSequence text) { // If this is the last character of a line, then we really should // have only added its width. The advance may include extra spacing // that we don't care about. - if (i == characters.length() - 1 || characters.charAt(i + 1) == '\n') { + if (i == processedText.length() - 1 || processedText.charAt(i + 1) == '\n') { if (rightToLeft) { // In RTL text we move the letter x0 by its xAdvance, so // we should add it to lineWidth. @@ -315,30 +374,54 @@ public float getLineWidth(CharSequence text) { return Math.max(maxLineWidth, lineWidth); } - /** - * Merge two fonts. - * If two font have the same style, merge will fail. - * @param newFont Style must be assigned to this. - * author: Yonghoon + * Merges another {@link BitmapFont} into this one. + * This operation combines the character sets and font pages. + * If both fonts contain the same style, the merge will fail and throw a RuntimeException. + * + * @param newFont The {@link BitmapFont} to merge into this one. It must have a style assigned. */ public void merge(BitmapFont newFont) { charSet.merge(newFont.charSet); final int size1 = this.pages.length; final int size2 = newFont.pages.length; - Material[] tmp = new Material[size1+size2]; + Material[] tmp = new Material[size1 + size2]; System.arraycopy(this.pages, 0, tmp, 0, size1); System.arraycopy(newFont.pages, 0, tmp, size1, size2); this.pages = tmp; - -// this.pages = Arrays.copyOf(this.pages, size1+size2); -// System.arraycopy(newFont.pages, 0, this.pages, size1, size2); } + /** + * Sets the style for the font's character set. + * This method is typically used when a font file contains only one style + * but needs to be assigned a specific style identifier for merging + * with other multi-style fonts. + * + * @param style The integer style identifier to set. + */ public void setStyle(int style) { charSet.setStyle(style); } -} \ No newline at end of file + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(charSet, "charSet", null); + oc.write(pages, "pages", null); + oc.write(rightToLeft, "rightToLeft", false); + oc.write(glyphParser, "glyphParser", 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); + rightToLeft = ic.readBoolean("rightToLeft", false); + glyphParser = (GlyphParser) ic.readSavable("glyphParser", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapText.java b/jme3-core/src/main/java/com/jme3/font/BitmapText.java index eee39bf482..6825ad77b0 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapText.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapText.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,20 +38,38 @@ import com.jme3.renderer.RenderManager; import com.jme3.scene.Node; import com.jme3.util.clone.Cloner; + import java.util.regex.Matcher; import java.util.regex.Pattern; /** + * `BitmapText` is a spatial node that displays text using a {@link BitmapFont}. + * It handles text layout, alignment, wrapping, coloring, and styling based on + * the properties set via its methods. The text is rendered as a series of + * quads (rectangles) with character textures from the font's pages. + * * @author YongHoon */ public class BitmapText extends Node { + // The font used to render this text. private BitmapFont font; + // Stores the text content and its layout properties (size, box, alignment, etc.). private StringBlock block; + // A flag indicating whether the text needs to be re-assembled private boolean needRefresh = true; + // An array of `BitmapTextPage` instances, each corresponding to a font page. private BitmapTextPage[] textPages; + // Manages the individual letter quads, their positions, colors, and styles. private Letters letters; + /** + * Creates a new `BitmapText` instance using the specified font. + * The text will be rendered left-to-right by default, unless the font itself + * is configured for right-to-left rendering. + * + * @param font The {@link BitmapFont} to use for rendering the text (not null). + */ public BitmapText(BitmapFont font) { this(font, font.isRightToLeft(), false); } @@ -69,6 +87,15 @@ public BitmapText(BitmapFont font, boolean rightToLeft) { this(font, rightToLeft, false); } + /** + * Creates a new `BitmapText` instance with the specified font, text direction, + * and a flag for array-based rendering. + * + * @param font The {@link BitmapFont} to use for rendering the text (not null). + * @param rightToLeft true for right-to-left text rendering, false for left-to-right. + * @param arrayBased If true, the internal text pages will use array-based buffers for rendering. + * This might affect performance or compatibility depending on the renderer. + */ public BitmapText(BitmapFont font, boolean rightToLeft, boolean arrayBased) { textPages = new BitmapTextPage[font.getPageSize()]; for (int page = 0; page < textPages.length; page++) { @@ -84,7 +111,7 @@ public BitmapText(BitmapFont font, boolean rightToLeft, boolean arrayBased) { @Override public BitmapText clone() { - return (BitmapText)super.clone(false); + return (BitmapText) super.clone(false); } /** @@ -114,13 +141,19 @@ public void cloneFields(Cloner cloner, Object original) { // so I guess cloning doesn't come up that often. } + /** + * Returns the {@link BitmapFont} currently used by this `BitmapText` instance. + * + * @return The {@link BitmapFont} object. + */ public BitmapFont getFont() { return font; } /** - * Changes text size - * @param size text size + * Sets the size of the text. This value scales the font's base character sizes. + * + * @param size The desired text size (e.g., in world units or pixels). */ public void setSize(float size) { block.setSize(size); @@ -128,13 +161,20 @@ public void setSize(float size) { letters.invalidate(); } + /** + * Returns the current size of the text. + * + * @return The text size. + */ public float getSize() { return block.getSize(); } /** + * Sets the text content to be displayed. * - * @param text charsequence to change text to + * @param text The `CharSequence` (e.g., `String` or `StringBuilder`) to display. + * If null, the text will be set to an empty string. */ public void setText(CharSequence text) { // note: text.toString() is free if text is already a java.lang.String. @@ -142,72 +182,50 @@ public void setText(CharSequence text) { } /** + * Sets the text content to be displayed. + * If the new text is the same as the current text, no update occurs. + * Otherwise, the internal `StringBlock` and `Letters` objects are updated, + * and a refresh is flagged to re-layout the text. * - * @param text String to change text to + * @param text The `String` to display. If null, the text will be set to an empty string. */ public void setText(String text) { text = text == null ? "" : text; - if (text == block.getText() || block.getText().equals(text)) { + if (block.getText().equals(text)) { return; } - /* - The problem with the below block is that StringBlock carries - pretty much all of the text-related state of the BitmapText such - as size, text box, alignment, etc. - - I'm not sure why this change was needed and the commit message was - not entirely helpful because it purports to fix a problem that I've - never encountered. - - If block.setText("") doesn't do the right thing then that's where - the fix should go because StringBlock carries too much information to - be blown away every time. -pspeed - - Change was made: - http://code.google.com/p/jmonkeyengine/source/detail?spec=svn9389&r=9389 - Diff: - http://code.google.com/p/jmonkeyengine/source/diff?path=/trunk/engine/src/core/com/jme3/font/BitmapText.java&format=side&r=9389&old_path=/trunk/engine/src/core/com/jme3/font/BitmapText.java&old=8843 - - // If the text is empty, reset - if (text.isEmpty()) { - detachAllChildren(); - - for (int page = 0; page < textPages.length; page++) { - textPages[page] = new BitmapTextPage(font, true, page); - attachChild(textPages[page]); - } - - block = new StringBlock(); - letters = new Letters(font, block, letters.getQuad().isRightToLeft()); - } - */ - // Update the text content block.setText(text); letters.setText(text); - - // Flag for refresh needRefresh = true; } /** - * @return returns text + * Returns the current text content displayed by this `BitmapText` instance. + * + * @return The text content as a `String`. */ public String getText() { return block.getText(); } /** - * @return color of the text + * Returns the base color applied to the entire text. + * Note: Substring colors set via `setColor(int, int, ColorRGBA)` or + * `setColor(String, ColorRGBA)` will override this base color for their respective ranges. + * + * @return The base {@link ColorRGBA} of the text. */ public ColorRGBA getColor() { return letters.getBaseColor(); } /** - * changes text color. all substring colors are deleted. - * @param color new color of text + * Sets the base color for the entire text. + * This operation will clear any previously set substring colors. + * + * @param color The new base {@link ColorRGBA} for the text. */ public void setColor(ColorRGBA color) { letters.setColor(color); @@ -216,26 +234,34 @@ public void setColor(ColorRGBA color) { } /** - * Sets an overall alpha that will be applied to all - * letters. If the alpha passed is -1 then alpha reverts - * to default... which will be 1 for anything unspecified - * and color tags will be reset to 1 or their encoded - * alpha. + * Sets an overall alpha (transparency) value that will be applied to all + * letters in the text. + * If the alpha passed is -1, the alpha reverts to its default behavior: + * 1.0 for unspecified parts, and the encoded alpha from any color tags. * - * @param alpha the desired alpha, or -1 to revert to the default + * @param alpha The desired alpha value (0.0 for fully transparent, 1.0 for fully opaque), + * or -1 to revert to default alpha behavior. */ public void setAlpha(float alpha) { letters.setBaseAlpha(alpha); needRefresh = true; } + /** + * Returns the current base alpha value applied to the text. + * + * @return The base alpha value, or -1 if default alpha behavior is active. + */ public float getAlpha() { return letters.getBaseAlpha(); } /** - * Define the area where the BitmapText will be rendered. - * @param rect position and size box where text is rendered + * Defines a rectangular bounding box within which the text will be rendered. + * This box is used for text wrapping and alignment. + * + * @param rect The {@link Rectangle} defining the position (x, y) and size (width, height) + * of the text rendering area. */ public void setBox(Rectangle rect) { block.setTextBox(rect); @@ -244,14 +270,19 @@ public void setBox(Rectangle rect) { } /** - * @return height of the line + * Returns the height of a single line of text, scaled by the current text size. + * + * @return The calculated line height. */ public float getLineHeight() { return font.getLineHeight(block); } /** - * @return height of whole text block + * Calculates and returns the total height of the entire text block, + * considering all lines and the defined text box (if any). + * + * @return The total height of the text block. */ public float getHeight() { if (needRefresh) { @@ -266,7 +297,9 @@ public float getHeight() { } /** - * @return width of line + * Calculates and returns the maximum width of any line in the text block. + * + * @return The maximum line width of the text. */ public float getLineWidth() { if (needRefresh) { @@ -282,7 +315,9 @@ public float getLineWidth() { } /** - * @return line count + * Returns the number of lines the text currently occupies. + * + * @return The total number of lines. */ public int getLineCount() { if (needRefresh) { @@ -291,14 +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. + * 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 alignment (such as Align.Left) + * @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) { @@ -310,9 +352,11 @@ public void setAlignment(BitmapFont.Align align) { } /** - * Set vertical alignment. Applicable only when text bound is set. + * 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 alignment (such as Align.Top) + * @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) { @@ -323,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 the style to apply + * 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 the style to apply + * 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); @@ -355,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 the desired 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); @@ -367,9 +426,10 @@ public void setColor(int start, int end, ColorRGBA color) { } /** - * Set the color of substring. - * @param regexp regular expression - * @param color the desired 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); @@ -382,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); @@ -391,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); @@ -401,10 +466,10 @@ public void setTabWidth(float width) { } /** - * for setLineWrapType(LineWrapType.NoWrap), - * set the last character when the text exceeds the bound. + * 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 indicate truncated text + * @param c The character to use as the ellipsis. */ public void setEllipsisChar(char c) { block.setEllipsisChar(c); @@ -413,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) { @@ -436,28 +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 quad list + // 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; } + /** + * 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/ColorTags.java b/jme3-core/src/main/java/com/jme3/font/ColorTags.java index a64806a785..304b9832a6 100644 --- a/jme3-core/src/main/java/com/jme3/font/ColorTags.java +++ b/jme3-core/src/main/java/com/jme3/font/ColorTags.java @@ -47,7 +47,7 @@ class ColorTags { private static final Pattern colorPattern = Pattern.compile("\\\\#([0-9a-fA-F]{8})#|\\\\#([0-9a-fA-F]{6})#|" + "\\\\#([0-9a-fA-F]{4})#|\\\\#([0-9a-fA-F]{3})#"); - final private LinkedList colors = new LinkedList<>(); + private final LinkedList colors = new LinkedList<>(); private String text; private String original; private float baseAlpha = -1; diff --git a/jme3-core/src/main/java/com/jme3/font/LetterQuad.java b/jme3-core/src/main/java/com/jme3/font/LetterQuad.java index 2611a059f2..ff647ab423 100644 --- a/jme3-core/src/main/java/com/jme3/font/LetterQuad.java +++ b/jme3-core/src/main/java/com/jme3/font/LetterQuad.java @@ -66,7 +66,7 @@ class LetterQuad { private LetterQuad next; private int colorInt = 0xFFFFFFFF; - final private boolean rightToLeft; + private final boolean rightToLeft; private float alignX; private float alignY; private float sizeScale = 1; diff --git a/jme3-core/src/main/java/com/jme3/font/Letters.java b/jme3-core/src/main/java/com/jme3/font/Letters.java index 0b9473d03e..b545dee035 100644 --- a/jme3-core/src/main/java/com/jme3/font/Letters.java +++ b/jme3-core/src/main/java/com/jme3/font/Letters.java @@ -47,10 +47,10 @@ class Letters { private final LetterQuad tail; private final BitmapFont font; private LetterQuad current; - final private StringBlock block; + private final StringBlock block; private float totalWidth; private float totalHeight; - final private ColorTags colorTags = new ColorTags(); + private final ColorTags colorTags = new ColorTags(); private ColorRGBA baseColor = null; private float baseAlpha = -1; private String plainText; diff --git a/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java index 9381f510b7..7eab91ee12 100644 --- a/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java +++ b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java @@ -42,13 +42,13 @@ */ public abstract class AbstractJoystick implements Joystick { - final private InputManager inputManager; - final private JoyInput joyInput; - final private int joyId; - final private String name; + private final InputManager inputManager; + private final JoyInput joyInput; + private final int joyId; + private final String name; - final private List axes = new ArrayList<>(); - final private List buttons = new ArrayList<>(); + private final List axes = new ArrayList<>(); + private final List buttons = new ArrayList<>(); /** * Creates a new joystick instance. Only used internally. diff --git a/jme3-core/src/main/java/com/jme3/input/CameraInput.java b/jme3-core/src/main/java/com/jme3/input/CameraInput.java index 1d40c716f4..b4e46c8ad2 100644 --- a/jme3-core/src/main/java/com/jme3/input/CameraInput.java +++ b/jme3-core/src/main/java/com/jme3/input/CameraInput.java @@ -44,37 +44,37 @@ public class CameraInput { * Chase camera mapping for moving down. Default assigned to * MouseInput.AXIS_Y direction depending on the invertYaxis configuration */ - public final static String CHASECAM_DOWN = "ChaseCamDown"; + public static final String CHASECAM_DOWN = "ChaseCamDown"; /** * Chase camera mapping for moving up. Default assigned to MouseInput.AXIS_Y * direction depending on the invertYaxis configuration */ - public final static String CHASECAM_UP = "ChaseCamUp"; + public static final String CHASECAM_UP = "ChaseCamUp"; /** * Chase camera mapping for zooming in. Default assigned to * MouseInput.AXIS_WHEEL direction positive */ - public final static String CHASECAM_ZOOMIN = "ChaseCamZoomIn"; + public static final String CHASECAM_ZOOMIN = "ChaseCamZoomIn"; /** * Chase camera mapping for zooming out. Default assigned to * MouseInput.AXIS_WHEEL direction negative */ - public final static String CHASECAM_ZOOMOUT = "ChaseCamZoomOut"; + public static final String CHASECAM_ZOOMOUT = "ChaseCamZoomOut"; /** * Chase camera mapping for moving left. Default assigned to * MouseInput.AXIS_X direction depending on the invertXaxis configuration */ - public final static String CHASECAM_MOVELEFT = "ChaseCamMoveLeft"; + public static final String CHASECAM_MOVELEFT = "ChaseCamMoveLeft"; /** * Chase camera mapping for moving right. Default assigned to * MouseInput.AXIS_X direction depending on the invertXaxis configuration */ - public final static String CHASECAM_MOVERIGHT = "ChaseCamMoveRight"; + public static final String CHASECAM_MOVERIGHT = "ChaseCamMoveRight"; /** * Chase camera mapping to initiate the rotation of the cam. Default assigned * to MouseInput.BUTTON_LEFT and MouseInput.BUTTON_RIGHT */ - public final static String CHASECAM_TOGGLEROTATE = "ChaseCamToggleRotate"; + public static final String CHASECAM_TOGGLEROTATE = "ChaseCamToggleRotate"; @@ -83,63 +83,63 @@ public class CameraInput { * Fly camera mapping to look left. Default assigned to MouseInput.AXIS_X, * direction negative */ - public final static String FLYCAM_LEFT = "FLYCAM_Left"; + public static final String FLYCAM_LEFT = "FLYCAM_Left"; /** * Fly camera mapping to look right. Default assigned to MouseInput.AXIS_X, * direction positive */ - public final static String FLYCAM_RIGHT = "FLYCAM_Right"; + public static final String FLYCAM_RIGHT = "FLYCAM_Right"; /** * Fly camera mapping to look up. Default assigned to MouseInput.AXIS_Y, * direction positive */ - public final static String FLYCAM_UP = "FLYCAM_Up"; + public static final String FLYCAM_UP = "FLYCAM_Up"; /** * Fly camera mapping to look down. Default assigned to MouseInput.AXIS_Y, * direction negative */ - public final static String FLYCAM_DOWN = "FLYCAM_Down"; + public static final String FLYCAM_DOWN = "FLYCAM_Down"; /** * Fly camera mapping to move left. Default assigned to KeyInput.KEY_A */ - public final static String FLYCAM_STRAFELEFT = "FLYCAM_StrafeLeft"; + public static final String FLYCAM_STRAFELEFT = "FLYCAM_StrafeLeft"; /** * Fly camera mapping to move right. Default assigned to KeyInput.KEY_D */ - public final static String FLYCAM_STRAFERIGHT = "FLYCAM_StrafeRight"; + public static final String FLYCAM_STRAFERIGHT = "FLYCAM_StrafeRight"; /** * Fly camera mapping to move forward. Default assigned to KeyInput.KEY_W */ - public final static String FLYCAM_FORWARD = "FLYCAM_Forward"; + public static final String FLYCAM_FORWARD = "FLYCAM_Forward"; /** * Fly camera mapping to move backward. Default assigned to KeyInput.KEY_S */ - public final static String FLYCAM_BACKWARD = "FLYCAM_Backward"; + public static final String FLYCAM_BACKWARD = "FLYCAM_Backward"; /** * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL, * direction positive */ - public final static String FLYCAM_ZOOMIN = "FLYCAM_ZoomIn"; + public static final String FLYCAM_ZOOMIN = "FLYCAM_ZoomIn"; /** * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL, * direction negative */ - public final static String FLYCAM_ZOOMOUT = "FLYCAM_ZoomOut"; + public static final String FLYCAM_ZOOMOUT = "FLYCAM_ZoomOut"; /** * Fly camera mapping to toggle rotation. Default assigned to * MouseInput.BUTTON_LEFT */ - public final static String FLYCAM_ROTATEDRAG = "FLYCAM_RotateDrag"; + public static final String FLYCAM_ROTATEDRAG = "FLYCAM_RotateDrag"; /** * Fly camera mapping to move up. Default assigned to KeyInput.KEY_Q */ - public final static String FLYCAM_RISE = "FLYCAM_Rise"; + public static final String FLYCAM_RISE = "FLYCAM_Rise"; /** * Fly camera mapping to move down. Default assigned to KeyInput.KEY_W */ - public final static String FLYCAM_LOWER = "FLYCAM_Lower"; + public static final String FLYCAM_LOWER = "FLYCAM_Lower"; - public final static String FLYCAM_INVERTY = "FLYCAM_InvertY"; + public static final String FLYCAM_INVERTY = "FLYCAM_InvertY"; /** * A private constructor to inhibit instantiation of this class. diff --git a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java index 057e729062..fe494801b3 100644 --- a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java @@ -105,37 +105,37 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control, Jme * @deprecated use {@link CameraInput#CHASECAM_DOWN} */ @Deprecated - public final static String ChaseCamDown = "ChaseCamDown"; + public static final String ChaseCamDown = "ChaseCamDown"; /** * @deprecated use {@link CameraInput#CHASECAM_UP} */ @Deprecated - public final static String ChaseCamUp = "ChaseCamUp"; + public static final String ChaseCamUp = "ChaseCamUp"; /** * @deprecated use {@link CameraInput#CHASECAM_ZOOMIN} */ @Deprecated - public final static String ChaseCamZoomIn = "ChaseCamZoomIn"; + public static final String ChaseCamZoomIn = "ChaseCamZoomIn"; /** * @deprecated use {@link CameraInput#CHASECAM_ZOOMOUT} */ @Deprecated - public final static String ChaseCamZoomOut = "ChaseCamZoomOut"; + public static final String ChaseCamZoomOut = "ChaseCamZoomOut"; /** * @deprecated use {@link CameraInput#CHASECAM_MOVELEFT} */ @Deprecated - public final static String ChaseCamMoveLeft = "ChaseCamMoveLeft"; + public static final String ChaseCamMoveLeft = "ChaseCamMoveLeft"; /** * @deprecated use {@link CameraInput#CHASECAM_MOVERIGHT} */ @Deprecated - public final static String ChaseCamMoveRight = "ChaseCamMoveRight"; + public static final String ChaseCamMoveRight = "ChaseCamMoveRight"; /** * @deprecated use {@link CameraInput#CHASECAM_TOGGLEROTATE} */ @Deprecated - public final static String ChaseCamToggleRotate = "ChaseCamToggleRotate"; + public static final String ChaseCamToggleRotate = "ChaseCamToggleRotate"; protected boolean zoomin; protected boolean hideCursorOnRotate = true; diff --git a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java index 6e6bff046f..ceb19bc280 100644 --- a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java @@ -40,14 +40,15 @@ */ public class DefaultJoystickAxis implements JoystickAxis { - final private InputManager inputManager; - final private Joystick parent; - final private int axisIndex; - final private String name; - final private String logicalId; - final private boolean isAnalog; - final private boolean isRelative; + private final InputManager inputManager; + private final Joystick parent; + private final int axisIndex; + private final String name; + private final String logicalId; + private final boolean isAnalog; + private final boolean isRelative; private float deadZone; + private float jitterThreshold = 0f; /** * Creates a new joystick axis instance. Only used internally. @@ -166,6 +167,12 @@ public void setDeadZone(float f) { 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 63cc1e9bfe..2dd45d4191 100644 --- a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java @@ -40,11 +40,11 @@ */ public class DefaultJoystickButton implements JoystickButton { - final private InputManager inputManager; - final private Joystick parent; - final private int buttonIndex; - final private String name; - final private String logicalId; + private final InputManager inputManager; + private final Joystick parent; + private final int buttonIndex; + private final String name; + private final String logicalId; public DefaultJoystickButton(InputManager inputManager, Joystick parent, int buttonIndex, String name, String logicalId) { diff --git a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java index 39e40725fd..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,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 @@ -56,25 +56,25 @@ */ public class FlyByCamera implements AnalogListener, ActionListener { - final 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) @@ -83,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) */ @@ -109,6 +109,15 @@ 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. * @@ -116,7 +125,7 @@ public class FlyByCamera implements AnalogListener, ActionListener { */ public FlyByCamera(Camera cam) { this.cam = cam; - initialUpVec = cam.getUp().clone(); + cam.getUp(initialUpVec); } /** @@ -128,63 +137,61 @@ 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; @@ -196,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); } } @@ -206,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() { @@ -245,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 (not null, alias created) + * @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)); @@ -266,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)); @@ -283,13 +290,19 @@ 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); } } } + /** + * 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 @@ -308,7 +321,7 @@ protected void mapJoystick(Joystick joystick) { // And let the dpad be up and down joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_RISE, CameraInput.FLYCAM_LOWER); - if( joystick.getButton("Button 8") != null) { + if (joystick.getButton("Button 8") != null) { // Let the standard select button be the y invert toggle joystick.getButton("Button 8").assignButton(CameraInput.FLYCAM_INVERTY); } @@ -322,7 +335,7 @@ protected void mapJoystick(Joystick joystick) { } /** - * Unregister this controller from its input manager. + * Unregisters this controller from its currently associated {@link InputManager}. */ public void unregisterInput() { if (inputManager == null) { @@ -338,97 +351,112 @@ public void unregisterInput() { 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; - } + 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) { - float newFov = cam.getFov() + value * 0.1F * zoomSpeed; - if (newFov > 0) { - cam.setFov(newFov); + 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); + } + + } 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(); + 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); + if (sideways) { + cam.getLeft(tempVel); } else { - cam.getDirection(vel); + 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); } /** @@ -448,9 +476,9 @@ public void onAnalog(String name, float value, float tpf) { } else if (name.equals(CameraInput.FLYCAM_RIGHT)) { rotateCamera(-value, initialUpVec); } else if (name.equals(CameraInput.FLYCAM_UP)) { - rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft()); + rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft(tempLeft)); } else if (name.equals(CameraInput.FLYCAM_DOWN)) { - rotateCamera(value * (invertY ? -1 : 1), cam.getLeft()); + 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)) { @@ -474,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); + 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/JoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java index fbbddb9f06..4168e32711 100644 --- a/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java +++ b/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java @@ -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"; @@ -128,4 +115,15 @@ public interface JoystickAxis { * @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 d8d2543ba0..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-2021 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 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 105440855d..d2385be4f9 100644 --- a/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java +++ b/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java @@ -44,6 +44,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.jme3.util.res.Resources; + /** * Provides compatibility mapping to different joysticks @@ -70,8 +72,8 @@ public class JoystickCompatibilityMappings { private static Map> buttonMappings = new HashMap>(); // Remaps names by regex. - final private static Map nameRemappings = new HashMap<>(); - final private static Map nameCache = new HashMap<>(); + private static final Map nameRemappings = new HashMap<>(); + private static final Map nameCache = new HashMap<>(); static { loadDefaultMappings(); @@ -554,9 +556,9 @@ public static void loadMappingProperties(URL u) throws IOException { } } - 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 { loadMappingProperties(u); @@ -574,7 +576,7 @@ protected static void loadMappings(ClassLoader cl, String path) throws IOExcepti protected static void loadDefaultMappings() { for (String s : searchPaths) { try { - loadMappings(JoystickCompatibilityMappings.class.getClassLoader(), s); + loadMappings(s); } catch (IOException e) { logger.log(Level.SEVERE, "Error searching resource path:{0}", s); } 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/event/JoyButtonEvent.java b/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java index 5fca5d5703..615bb90212 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java @@ -41,8 +41,8 @@ */ public class JoyButtonEvent extends InputEvent { - final private JoystickButton button; - final private boolean pressed; + private final JoystickButton button; + private final boolean pressed; public JoyButtonEvent(JoystickButton button, boolean pressed) { this.button = button; diff --git a/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java b/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java index f693d2caad..2fb0294730 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java @@ -40,10 +40,10 @@ */ public class KeyInputEvent extends InputEvent { - final private int keyCode; - final private char keyChar; - final private boolean pressed; - final private boolean repeating; + private final int keyCode; + private final char keyChar; + private final boolean pressed; + private final boolean repeating; public KeyInputEvent(int keyCode, char keyChar, boolean pressed, boolean repeating) { this.keyCode = keyCode; diff --git a/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java b/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java index 83c383cef7..0d5ddc0a52 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java @@ -40,10 +40,10 @@ */ public class MouseButtonEvent extends InputEvent { - final private int x; - final private int y; - final private int btnIndex; - final private boolean pressed; + private final int x; + private final int y; + private final int btnIndex; + private final boolean pressed; public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) { this.btnIndex = btnIndex; diff --git a/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java b/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java index e88c09c1b6..0bf269f3cc 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java @@ -40,7 +40,7 @@ */ public class MouseMotionEvent extends InputEvent { - final private int x, y, dx, dy, wheel, deltaWheel; + private final int x, y, dx, dy, wheel, deltaWheel; public MouseMotionEvent(int x, int y, int dx, int dy, int wheel, int deltaWheel) { this.x = x; diff --git a/jme3-core/src/main/java/com/jme3/light/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/DirectionalLight.java b/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java index 53528cf9d0..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 @@ -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 4cbea59208..cebb4ae2a0 100644 --- a/jme3-core/src/main/java/com/jme3/light/Light.java +++ b/jme3-core/src/main/java/com/jme3/light/Light.java @@ -87,7 +87,7 @@ public enum Type { Probe(4); - final private int typeId; + private final int typeId; Type(int type){ this.typeId = type; diff --git a/jme3-core/src/main/java/com/jme3/light/LightProbe.java b/jme3-core/src/main/java/com/jme3/light/LightProbe.java index c2c9a584be..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-2021 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,7 +71,8 @@ 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[] shCoefficients; private TextureCubeMap prefilteredEnvMap; @@ -149,16 +150,12 @@ public void setPrefilteredMap(TextureCubeMap prefilteredEnvMap) { * @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); @@ -180,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()); } @@ -200,7 +197,6 @@ public void read(JmeImporter im) throws IOException { } } - /** * returns the bounding volume of this LightProbe * @return a bounding volume. @@ -318,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/PointLight.java b/jme3-core/src/main/java/com/jme3/light/PointLight.java index 959147c9a0..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 @@ -165,6 +165,13 @@ 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; @@ -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/SphereProbeArea.java b/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java index 89f7c07635..bf5bd1d539 100644 --- a/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java +++ b/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java @@ -13,7 +13,7 @@ public class SphereProbeArea implements ProbeArea { private Vector3f center = new Vector3f(); private float radius = 1; - final private Matrix4f uniformMatrix = new Matrix4f(); + private final Matrix4f uniformMatrix = new Matrix4f(); public SphereProbeArea() { } diff --git a/jme3-core/src/main/java/com/jme3/light/SpotLight.java b/jme3-core/src/main/java/com/jme3/light/SpotLight.java index 417e845f0b..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -118,8 +118,7 @@ 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. @@ -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); @@ -458,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 0f3013cba6..5c5da45d8e 100644 --- a/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java +++ b/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java @@ -45,7 +45,7 @@ */ public class WeightedProbeBlendingStrategy implements LightProbeBlendingStrategy { - private final static int MAX_PROBES = 3; + private static final int MAX_PROBES = 3; List lightProbes = new ArrayList<>(); @Override diff --git a/jme3-core/src/main/java/com/jme3/material/MatParam.java b/jme3-core/src/main/java/com/jme3/material/MatParam.java index 35111331be..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-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,16 +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; -import java.util.Arrays; - /** * Describes a material parameter. This is used for both defining a name and type * as well as a material parameter value. @@ -58,8 +68,8 @@ public class MatParam implements Savable, Cloneable { /** * Create a new material parameter. For internal use only. * - * @param type the type of the parameter - * @param name the desired parameter name + * @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) { @@ -75,20 +85,19 @@ public MatParam(VarType type, String name, Object value) { protected MatParam() { } - public boolean isTypeCheckEnabled() { return typeCheck; } - /** * Enable type check for this param. * When type check is enabled a RuntimeException is thrown if * an object of the wrong type is passed to setValue. - * @param v (default = true) + * + * @param typeCheck (default = true) */ - public void setTypeCheckEnabled(boolean v) { - typeCheck = v; + public void setTypeCheckEnabled(boolean typeCheck) { + this.typeCheck = typeCheck; } /** @@ -102,6 +111,7 @@ public VarType getVarType() { /** * Returns the name of the material parameter. + * * @return the name of the material parameter. */ public String getName() { @@ -158,15 +168,16 @@ public void setValue(Object value) { } } 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())); + 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. @@ -274,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"); + 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"; + return texVal + ":returned null key"; } String ret = ""; @@ -287,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() + "\""; @@ -315,12 +326,12 @@ private String getWrapMode(Texture texVal, Texture.WrapAxis axis) { WrapMode mode = WrapMode.EdgeClamp; 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() + " "; + return "Wrap" + mode.name() + "_" + axis.name() + " "; } return ""; } diff --git a/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java index 187eebc72d..58bd44418d 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,51 +40,67 @@ import com.jme3.texture.image.ColorSpace; import java.io.IOException; +/** + * A material parameter that holds a reference to a texture and its required color space. + * This class extends {@link MatParam} to provide texture specific functionalities. + */ public class MatParamTexture extends MatParam { - private Texture texture; private ColorSpace colorSpace; + /** + * Constructs a new MatParamTexture instance with the specified type, name, + * texture, and color space. + * + * @param type the type of the material parameter + * @param name the name of the parameter + * @param texture the texture associated with this parameter + * @param colorSpace the required color space for the texture + */ public MatParamTexture(VarType type, String name, Texture texture, ColorSpace colorSpace) { super(type, name, texture); - this.texture = texture; this.colorSpace = colorSpace; } + /** + * Serialization only. Do not use. + */ public MatParamTexture() { } + /** + * Retrieves the texture associated with this material parameter. + * + * @return the texture object + */ public Texture getTextureValue() { - return texture; + return (Texture) getValue(); } + /** + * Sets the texture associated with this material parameter. + * + * @param value the texture object to set + * @throws RuntimeException if the provided value is not a {@link Texture} + */ public void setTextureValue(Texture value) { - this.value = value; - this.texture = value; - } - - @Override - public void setValue(Object value) { - if (!(value instanceof Texture)) { - throw new IllegalArgumentException("value must be a texture object"); - } - this.value = value; - this.texture = (Texture) value; + setValue(value); } /** + * Gets the required color space for this texture parameter. * - * @return the color space required by this texture param + * @return the required color space ({@link ColorSpace}) */ public ColorSpace getColorSpace() { return colorSpace; } /** - * Set to {@link ColorSpace#Linear} if the texture color space has to be forced to linear - * instead of sRGB + * Set to {@link ColorSpace#Linear} if the texture color space has to be forced + * to linear instead of sRGB. + * * @param colorSpace the desired color space - * @see ColorSpace */ public void setColorSpace(ColorSpace colorSpace) { this.colorSpace = colorSpace; @@ -94,17 +110,17 @@ public void setColorSpace(ColorSpace colorSpace) { public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); - oc.write(0, "texture_unit", -1); - oc.write(texture, "texture", null); // For backwards compatibility - oc.write(colorSpace, "colorSpace", null); + // For backwards compatibility + oc.write(0, "texture_unit", -1); + oc.write((Texture) value, "texture", null); } @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); - texture = (Texture) value; colorSpace = ic.readEnum("colorSpace", ColorSpace.class, null); } -} \ No newline at end of file + +} 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 926db40236..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,12 +34,20 @@ 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; @@ -47,14 +55,20 @@ 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; @@ -75,18 +89,37 @@ 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 Technique technique; private HashMap techniques = new HashMap<>(); private RenderState additionalState = null; - final private RenderState mergedRenderState = new RenderState(); + private final RenderState mergedRenderState = new RenderState(); private boolean transparent = false; private boolean receivesShadows = false; private int sortingId = -1; + /** + * 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 IllegalArgumentException("Material definition cannot be null"); @@ -101,40 +134,48 @@ public Material(MaterialDef def) { } } - public Material(AssetManager contentMan, String defName) { - this(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; @@ -216,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(). @@ -393,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. @@ -440,7 +481,7 @@ public MatParamTexture getTextureParam(String name) { } return null; } - + /** * Returns a collection of all parameters set on this material. * @@ -482,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 */ @@ -494,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); @@ -503,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 @@ -541,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) { @@ -585,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) { @@ -679,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); } /** @@ -696,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); } /** @@ -797,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(); @@ -810,96 +866,110 @@ private int applyOverrides(Renderer renderer, Shader shader, SafeArrayList worldOverrides, SafeArrayList forcedOverrides) { + 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()) { - try { - renderer.setTexture(unit, (Texture) param.getValue()); - } catch (TextureUnitException exception) { - int numTexParams = unit + 1; - String message = "Too many texture parameters (" - + numTexParams + ") assigned\n to " + toString(); - throw new IllegalStateException(message); - } - 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; } /** @@ -1028,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(); @@ -1043,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); } /** @@ -1066,24 +1136,25 @@ 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) + - "]"; - } - @Override @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { @@ -1092,12 +1163,13 @@ public void read(JmeImporter im) throws IOException { 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; @@ -1113,7 +1185,7 @@ 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")) { @@ -1165,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()); @@ -1185,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/Materials.java b/jme3-core/src/main/java/com/jme3/material/Materials.java index 8ddc7b75d3..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,13 +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"; /** * A private constructor to inhibit instantiation of this class. */ private Materials() { } -} \ No newline at end of file +} 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 e0be1f0563..2e93a4e1f3 100644 --- a/jme3-core/src/main/java/com/jme3/material/RenderState.java +++ b/jme3-core/src/main/java/com/jme3/material/RenderState.java @@ -1511,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(); @@ -1623,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; @@ -1636,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; @@ -1665,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; @@ -1685,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() { @@ -1711,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/Technique.java b/jme3-core/src/main/java/com/jme3/material/Technique.java index 667594f09c..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-2021 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; @@ -162,9 +163,9 @@ Shader makeCurrent(RenderManager renderManager, SafeArrayList * @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 daa08d3266..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-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -422,11 +422,6 @@ public VarType getDefineIdType(int defineId) { 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); @@ -445,11 +440,6 @@ public void addShaderParamDefine(String paramName, VarType paramType, String def 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; @@ -834,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 9bb317219f..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-2021 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; @@ -91,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 d5c48b1df9..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-2021 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"); 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 801d5b7837..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-2021 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.*; +import com.jme3.material.Material.BindUnits; import com.jme3.material.RenderState.BlendMode; import com.jme3.math.*; import com.jme3.renderer.*; @@ -54,7 +55,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique private boolean useAmbientLight; private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); - final private List lightProbes = new ArrayList<>(3); + private final List lightProbes = new ArrayList<>(3); static { ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive); @@ -262,22 +263,21 @@ private int setProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeDa } @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 a9b51783c2..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-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ import com.jme3.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; @@ -206,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 5afee8f4b0..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-2021 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; @@ -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 d021d28d0f..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-2021 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; @@ -92,5 +93,5 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager * @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 87dc01d51f..df1ef1aa01 100644 --- a/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java +++ b/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -79,4 +79,20 @@ public abstract class AbstractTriangle implements Collidable { public int collideWith(Collidable other, CollisionResults results) { return other.collideWith(this, results); } + + /** + * Returns a string representation of the triangle, which is unaffected. For + * example, a {@link com.jme3.math.Triangle} joining (1,0,0) and (0,1,0) + * with (0,0,1) is represented by: + *

    +     * Triangle [V1: (1.0, 0.0, 0.0)  V2: (0.0, 1.0, 0.0)  V3: (0.0, 0.0, 1.0)]
    +     * 
    + * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [V1: " + get1() + " V2: " + + get2() + " V3: " + get3() + "]"; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java index 03cde0991d..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,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,18 +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 + * 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; @@ -153,15 +158,15 @@ 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 rgba) { - this.a = rgba.a; - this.r = rgba.r; - this.g = rgba.g; - this.b = rgba.b; + 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. @@ -170,12 +175,9 @@ public ColorRGBA(ColorRGBA rgba) { * values copied to this color's r, g, b, and a values respectively. */ public ColorRGBA(Vector4f vec4) { - this.a = vec4.w; - this.r = vec4.x; - this.g = vec4.y; - this.b = vec4.z; - } - + set(vec4); + } + /** * Constructor creates a new ColorRGBA object, based on * a provided Vector3f, at full opacity with a 1.0 alpha value by default @@ -185,10 +187,8 @@ public ColorRGBA(Vector4f vec4) { */ public ColorRGBA(Vector3f vec3) { this.a = 1.0f; - this.r = vec3.x; - this.g = vec3.y; - this.b = vec3.z; - } + set(vec3); + } /** * set sets the RGBA values of this ColorRGBA. @@ -214,24 +214,24 @@ public ColorRGBA set(float r, float g, float b, float a) { * 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. @@ -254,8 +254,8 @@ public ColorRGBA set(Vector4f vec4) { a = vec4.w; } return this; - } - + } + /** * set sets the values of this ColorRGBA to those * set by a parameter Vector3f. @@ -276,7 +276,7 @@ public ColorRGBA set(Vector3f vec3) { b = vec3.z; } return this; - } + } /** * Sets the red color to the specified value. @@ -336,22 +336,18 @@ public void clamp() { * @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); } /** @@ -401,11 +397,7 @@ public float getGreen() { * @return this ColorRGBA */ public ColorRGBA interpolateLocal(ColorRGBA finalColor, float changeAmount) { - this.r = (1 - changeAmount) * this.r + changeAmount * finalColor.r; - this.g = (1 - changeAmount) * this.g + changeAmount * finalColor.g; - this.b = (1 - changeAmount) * this.b + changeAmount * finalColor.b; - this.a = (1 - changeAmount) * this.a + changeAmount * finalColor.a; - return this; + return interpolateLocal(this, finalColor, changeAmount); } /** @@ -416,7 +408,7 @@ public ColorRGBA interpolateLocal(ColorRGBA finalColor, float changeAmount) { * @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. + * change from beginColor towards finalColor. * @return this ColorRGBA */ public ColorRGBA interpolateLocal(ColorRGBA beginColor, ColorRGBA finalColor, float changeAmount) { @@ -434,11 +426,11 @@ public ColorRGBA interpolateLocal(ColorRGBA beginColor, ColorRGBA finalColor, fl * @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); } /** @@ -535,19 +527,19 @@ 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; } /** @@ -605,32 +597,32 @@ public int hashCode() { * Serialize this color to the specified exporter, for example when * saving to a J3O file. * - * @param e (not null) + * @param ex (not null) * @throws IOException from the exporter */ @Override - 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); + 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); } /** * De-serialize this color from the specified importer, for example when * loading from a J3O file. * - * @param importer (not null) + * @param im (not null) * @throws IOException from the importer */ @Override - public void read(JmeImporter importer) throws IOException { - InputCapsule capsule = importer.getCapsule(this); - r = capsule.readFloat("r", 0); - g = capsule.readFloat("g", 0); - b = capsule.readFloat("b", 0); - a = capsule.readFloat("a", 0); + 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); } /** @@ -641,10 +633,10 @@ public void read(JmeImporter importer) throws IOException { */ 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; } @@ -656,11 +648,7 @@ public byte[] asBytesRGBA() { * @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); } /** @@ -671,11 +659,7 @@ public int asIntARGB() { * @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); } /** @@ -686,11 +670,7 @@ public int asIntRGBA() { * @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); } /** @@ -702,10 +682,10 @@ public int asIntABGR() { * @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; } @@ -717,10 +697,10 @@ public ColorRGBA fromIntARGB(int color) { * @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; } @@ -732,10 +712,10 @@ public ColorRGBA fromIntRGBA(int color) { * @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; } @@ -825,4 +805,29 @@ public ColorRGBA getAsSrgb() { 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/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java index 712e322e30..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,9 +40,10 @@ * @author Various * @version $Id: FastMath.java,v 1.45 2007/08/26 08:44:20 irrisor Exp $ */ -final public class FastMath { - private FastMath() { - } +public final class FastMath { + + private FastMath() {} + /** * A "close to zero" double epsilon value for use */ @@ -489,10 +490,8 @@ public static float acos(float fValue) { if (fValue < 1.0f) { return (float) Math.acos(fValue); } - return 0.0f; } - return PI; } @@ -511,10 +510,8 @@ public static float asin(float fValue) { if (fValue < 1.0f) { return (float) Math.asin(fValue); } - return HALF_PI; } - return -HALF_PI; } @@ -844,35 +841,111 @@ public static float determinant(double m00, double m01, double m02, } /** - * Returns a random float between 0 and 1. + * Generates a pseudorandom {@code float} in the range [0.0, 1.0). * - * @return a random float between 0 (inclusive) and 1 (exclusive) + * @return A random {@code float} value. */ public static float nextRandomFloat() { return rand.nextFloat(); } /** - * Returns a random integer between min and max. + * Generates a pseudorandom {@code float} in the range [min, max) * - * @param min the desired minimum value - * @param max the desired maximum value - * @return a random int between min (inclusive) and max (inclusive) + * @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(); } /** - * Choose a pseudo-random, uniformly-distributed integer value from - * the shared generator. + * Generates a pseudorandom, uniformly-distributed {@code int} value. * - * @return the next integer 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. @@ -883,8 +956,7 @@ public static int nextRandomInt() { * @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(); } @@ -906,8 +978,7 @@ public static Vector3f sphericalToCartesian(Vector3f sphereCoords, * @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(); } @@ -936,8 +1007,7 @@ public static Vector3f cartesianToSpherical(Vector3f cartCoords, * @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(); } @@ -959,8 +1029,7 @@ public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords, * @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(); } @@ -982,12 +1051,9 @@ 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 min - * the lower limit of the range - * @param max - * the upper limit of the range + * @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) { @@ -1135,4 +1201,12 @@ public static short convertFloatToHalf(float flt) { 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 f215dba4d6..122b05225e 100644 --- a/jme3-core/src/main/java/com/jme3/math/Line.java +++ b/jme3-core/src/main/java/com/jme3/math/Line.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -274,4 +274,20 @@ public Line clone() { throw new AssertionError(); } } + + /** + * Returns a string representation of the Line, which is unaffected. For + * example, a line with origin (1,0,0) and direction (0,1,0) is represented + * by: + *
    +     * Line [Origin: (1.0, 0.0, 0.0)  Direction: (0.0, 1.0, 0.0)]
    +     * 
    + * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [Origin: " + origin + + " Direction: " + direction + "]"; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/LineSegment.java b/jme3-core/src/main/java/com/jme3/math/LineSegment.java index a1436cd518..a840f1cd09 100644 --- a/jme3-core/src/main/java/com/jme3/math/LineSegment.java +++ b/jme3-core/src/main/java/com/jme3/math/LineSegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -720,6 +720,23 @@ public LineSegment clone() { } } + /** + * Returns a string representation of the LineSegment, which is unaffected. + * For example, a segment extending from (1,0,0) to (1,1,0) is represented + * by: + *
    +     * LineSegment [Origin: (1.0, 0.0, 0.0)  Direction: (0.0, 1.0, 0.0)  Extent: 1.0]
    +     * 
    + * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [Origin: " + origin + + " Direction: " + direction + " Extent: " + extent + "]"; + } + + /** /** *

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

    This function is float error aware.

    diff --git a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java index 6c6e7449e2..4f9a6e2f35 100644 --- a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java @@ -1802,6 +1802,9 @@ public Vector3f toTranslationVector(Vector3f vector) { /** * 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() { @@ -1813,6 +1816,9 @@ public Quaternion toRotationQuat() { /** * 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 */ @@ -1824,7 +1830,10 @@ public Quaternion toRotationQuat(Quaternion q) { /** * Determine the rotation component of this 3-D coordinate transform. * - * @return a new rotation Matrix3f + *

    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); @@ -1833,6 +1842,9 @@ public Matrix3f toRotationMatrix() { /** * 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) { @@ -1850,6 +1862,9 @@ public void toRotationMatrix(Matrix3f mat) { /** * 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() { @@ -1861,6 +1876,9 @@ public Vector3f toScaleVector() { /** * 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 storage for the result (not null, modified) * @return the scale factors (in {@code store}) for chaining */ 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 0974a38b7e..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-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -356,8 +356,10 @@ public float[] toAngles(float[] angles) { } /** - * Sets the quaternion from the specified rotation matrix. Does not verify - * that the argument is a valid rotation matrix. + * 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) @@ -369,7 +371,9 @@ public Quaternion fromRotationMatrix(Matrix3f matrix) { /** * Sets the quaternion from a rotation matrix with the specified elements. - * Does not verify that the arguments form a valid rotation matrix. + * + *

    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 @@ -385,7 +389,7 @@ public Quaternion fromRotationMatrix(Matrix3f matrix) { 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); @@ -564,7 +568,7 @@ public Matrix4f toTransformMatrix(Matrix4f store) { * current instance is unaffected. * *

    Note: preserves the translation and scaling components of - * {@code result}. + * {@code result} unless {@code result} includes reflection. * *

    Note: the result is created from a normalized version of the current * instance. @@ -651,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; @@ -1013,6 +1017,9 @@ public Quaternion mult(Quaternion q, Quaternion storeResult) { /** * Applies the rotation represented by the argument to the current instance. * + *

    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) { @@ -1095,6 +1102,10 @@ public void toAxes(Vector3f axes[]) { * Rotates the argument vector and returns the result as a new 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. + * *

    Despite the name, the result differs from the mathematical definition * of vector-quaternion multiplication. * @@ -1109,6 +1120,10 @@ public Vector3f mult(Vector3f v) { * 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. * @@ -1177,6 +1192,10 @@ public Quaternion multLocal(float qx, float qy, float qz, float qw) { * 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 @@ -1589,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/Rectangle.java b/jme3-core/src/main/java/com/jme3/math/Rectangle.java index 570e16f896..67cfcb15d7 100644 --- a/jme3-core/src/main/java/com/jme3/math/Rectangle.java +++ b/jme3-core/src/main/java/com/jme3/math/Rectangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -253,4 +253,19 @@ public Rectangle clone() { throw new AssertionError(); } } + + /** + * Returns a string representation of the Recatangle, which is unaffected. + * For example, a rectangle with vertices at (1,0,0), (2,0,0), (1,2,0), and + * (2,2,0) is represented by: + *

    +     * Rectangle [A: (1.0, 0.0, 0.0)  B: (2.0, 0.0, 0.0)  C: (1.0, 2.0, 0.0)]
    +     * 
    + * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [A: " + a + " B: " + b + " C: " + c + "]"; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/Ring.java b/jme3-core/src/main/java/com/jme3/math/Ring.java index c6d426d8a7..c1a17f3cf4 100644 --- a/jme3-core/src/main/java/com/jme3/math/Ring.java +++ b/jme3-core/src/main/java/com/jme3/math/Ring.java @@ -47,7 +47,7 @@ public final class Ring implements Savable, Cloneable, java.io.Serializable { private Vector3f center, up; private float innerRadius, outerRadius; - private transient static Vector3f b1 = new Vector3f(), b2 = new Vector3f(); + private static transient Vector3f b1 = new Vector3f(), b2 = new Vector3f(); /** * Constructor creates a new Ring lying on the XZ plane, diff --git a/jme3-core/src/main/java/com/jme3/math/Spline.java b/jme3-core/src/main/java/com/jme3/math/Spline.java index d707d10a3f..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-2021 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, @@ -480,7 +482,18 @@ 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); } @@ -506,13 +519,60 @@ public void read(JmeImporter im) throws IOException { 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 = 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 c172d26d4a..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-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -121,6 +121,7 @@ public Transform() { * @return the (modified) current instance (for chaining) */ public Transform setRotation(Quaternion rot) { + assert Quaternion.isValidQuaternion(rot) : "Invalid rotation " + rot; this.rot.set(rot); return this; } @@ -132,6 +133,7 @@ public Transform setRotation(Quaternion rot) { * @return the (modified) current instance (for chaining) */ public Transform setTranslation(Vector3f trans) { + assert Vector3f.isValidVector(trans) : "Invalid translation " + trans; this.translation.set(trans); return this; } @@ -152,6 +154,7 @@ public Vector3f getTranslation() { * @return the (modified) current instance (for chaining) */ public Transform setScale(Vector3f scale) { + assert Vector3f.isValidVector(scale) : "Invalid scale " + scale; this.scale.set(scale); return this; } @@ -163,6 +166,7 @@ public Transform setScale(Vector3f scale) { * @return the (modified) current instance (for chaining) */ public Transform setScale(float scale) { + assert Float.isFinite(scale) : "Invalid scale " + scale; this.scale.set(scale, scale, scale); return this; } @@ -257,8 +261,8 @@ public void interpolateTransforms(Transform t1, Transform t2, float delta) { * 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, unaffected unless it's - * this) + * @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) { @@ -286,6 +290,7 @@ public Transform combineWithParent(Transform parent) { * @return the (modified) current instance (for chaining) */ public Transform setTranslation(float x, float y, float z) { + assert Float.isFinite(x) && Float.isFinite(y) && Float.isFinite(z) : "Invalid translation " + x + ", " + y + ", " + z; translation.set(x, y, z); return this; } @@ -299,6 +304,7 @@ public Transform setTranslation(float x, float y, float z) { * @return the (modified) current instance (for chaining) */ public Transform setScale(float x, float y, float z) { + assert Float.isFinite(x) && Float.isFinite(y) && Float.isFinite(z) : "Invalid scale " + x + ", " + y + ", " + z; scale.set(x, y, z); return this; } @@ -308,6 +314,10 @@ public Transform setScale(float x, float y, float z) { * 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) @@ -330,6 +340,10 @@ public Vector3f transformVector(final Vector3f in, Vector3f store) { * 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) @@ -381,10 +395,13 @@ public Matrix4f toTransformMatrix(Matrix4f store) { } /** - * Sets the current instance from a transform matrix. Any shear in the + * 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) { @@ -398,6 +415,10 @@ public void fromTransformMatrix(Matrix4f mat) { /** * 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() { 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 1bec9c786b..e32871d75b 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector2f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector2f.java @@ -54,10 +54,34 @@ public final class Vector2f implements Savable, Cloneable, java.io.Serializable * 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); + /** + * 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. */ diff --git a/jme3-core/src/main/java/com/jme3/math/Vector3f.java b/jme3-core/src/main/java/com/jme3/math/Vector3f.java index 89efe8a6fd..fa5a8539cd 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector3f.java @@ -52,32 +52,32 @@ public final class Vector3f implements Savable, Cloneable, java.io.Serializable /** * Shared instance of the all-zero vector (0,0,0). Do not modify! */ - public final static Vector3f ZERO = new Vector3f(0, 0, 0); + public static final Vector3f ZERO = new Vector3f(0, 0, 0); /** * Shared instance of the all-NaN vector (NaN,NaN,NaN). Do not modify! */ - public final static Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); + public static final Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); /** * Shared instance of the +X direction (1,0,0). Do not modify! */ - public final static Vector3f UNIT_X = new Vector3f(1, 0, 0); + public static final Vector3f UNIT_X = new Vector3f(1, 0, 0); /** * Shared instance of the +Y direction (0,1,0). Do not modify! */ - public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0); + public static final Vector3f UNIT_Y = new Vector3f(0, 1, 0); /** * Shared instance of the +Z direction (0,0,1). Do not modify! */ - public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1); + public static final Vector3f UNIT_Z = new Vector3f(0, 0, 1); /** * Shared instance of the all-ones vector (1,1,1). Do not modify! */ - public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); + public static final Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); /** * Shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf). Do not * modify! */ - public final static Vector3f POSITIVE_INFINITY = new Vector3f( + public static final Vector3f POSITIVE_INFINITY = new Vector3f( Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); @@ -85,7 +85,7 @@ public final class Vector3f implements Savable, Cloneable, java.io.Serializable * Shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf). Do * not modify! */ - public final static Vector3f NEGATIVE_INFINITY = new Vector3f( + public static final Vector3f NEGATIVE_INFINITY = new Vector3f( Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); diff --git a/jme3-core/src/main/java/com/jme3/math/Vector4f.java b/jme3-core/src/main/java/com/jme3/math/Vector4f.java index 826cf29030..cf18c4be50 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector4f.java @@ -51,36 +51,36 @@ public final class Vector4f implements Savable, Cloneable, java.io.Serializable /** * shared instance of the all-zero vector (0,0,0,0) - Do not modify! */ - public final static Vector4f ZERO = new Vector4f(0, 0, 0, 0); + public static final Vector4f ZERO = new Vector4f(0, 0, 0, 0); /** * shared instance of the all-NaN vector (NaN,NaN,NaN,NaN) - Do not modify! */ - public final static Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN); + public static final Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN); /** * shared instance of the +X direction (1,0,0,0) - Do not modify! */ - public final static Vector4f UNIT_X = new Vector4f(1, 0, 0, 0); + public static final Vector4f UNIT_X = new Vector4f(1, 0, 0, 0); /** * shared instance of the +Y direction (0,1,0,0) - Do not modify! */ - public final static Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0); + public static final Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0); /** * shared instance of the +Z direction (0,0,1,0) - Do not modify! */ - public final static Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0); + public static final Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0); /** * shared instance of the +W direction (0,0,0,1) - Do not modify! */ - public final static Vector4f UNIT_W = new Vector4f(0, 0, 0, 1); + public static final Vector4f UNIT_W = new Vector4f(0, 0, 0, 1); /** * shared instance of the all-ones vector (1,1,1,1) - Do not modify! */ - public final static Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1); + public static final Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1); /** * shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf,+Inf) * - Do not modify! */ - public final static Vector4f POSITIVE_INFINITY = new Vector4f( + public static final Vector4f POSITIVE_INFINITY = new Vector4f( Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, @@ -89,7 +89,7 @@ public final class Vector4f implements Savable, Cloneable, java.io.Serializable * shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf,-Inf) * - Do not modify! */ - public final static Vector4f NEGATIVE_INFINITY = new Vector4f( + public static final Vector4f NEGATIVE_INFINITY = new Vector4f( Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, diff --git a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java index 42748855cd..bb5d95b6dd 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java +++ b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java @@ -53,8 +53,8 @@ public static OpenCLObjectManager getInstance() { return INSTANCE; } - final private ReferenceQueue refQueue = new ReferenceQueue<>(); - final private HashSet activeObjects = new HashSet<>(); + private final ReferenceQueue refQueue = new ReferenceQueue<>(); + private final HashSet activeObjects = new HashSet<>(); private static class OpenCLObjectRef extends PhantomReference { diff --git a/jme3-core/src/main/java/com/jme3/post/Filter.java b/jme3-core/src/main/java/com/jme3/post/Filter.java index 4852a5a848..efdb19b04a 100644 --- a/jme3-core/src/main/java/com/jme3/post/Filter.java +++ b/jme3-core/src/main/java/com/jme3/post/Filter.java @@ -108,7 +108,7 @@ public Pass() { 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.setDepthTarget(FrameBufferTarget.newTarget(depthBufferFormat)); if (renderDepth) { @@ -126,7 +126,7 @@ public void init(Renderer renderer, int width, int height, Format textureFormat, } renderFrameBuffer.addColorTarget(FrameBufferTarget.newTarget(renderedTexture)); - + renderFrameBuffer.setName(getClass().getSimpleName()); } 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 f7b6d185df..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-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,18 +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.texture.FrameBuffer.FrameBufferTarget; import com.jme3.ui.Picture; import com.jme3.util.SafeArrayList; + import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -52,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; @@ -89,23 +108,28 @@ public class FilterPostProcessor implements SceneProcessor, Savable { 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) { @@ -118,13 +142,14 @@ public void addFilter(Filter filter) { } setFilterState(filter, filter.isEnabled()); - } /** - * removes this filters from the filters list + * Removes a specific filter from the list. The filter's `cleanup` method + * is called upon removal. * - * @param filter the Filter to remove (not null) + * @param filter The filter to remove (not null). + * @throws IllegalArgumentException If the provided filter is null. */ public void removeFilter(Filter filter) { if (filter == null) { @@ -135,10 +160,22 @@ 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; @@ -148,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 dimensions + // 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.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 { @@ -194,45 +247,52 @@ 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 { + // 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.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; @@ -242,30 +302,44 @@ public boolean isInitialized() { 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) { @@ -275,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) { @@ -286,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) { @@ -303,23 +381,31 @@ private void renderFilterChain(Renderer r, FrameBuffer sceneFb) { } } + // 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); @@ -337,10 +423,14 @@ public void postFrame(FrameBuffer out) { } 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); } @@ -349,40 +439,44 @@ public void postFrame(FrameBuffer out) { @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 in a multiview situation we need to resize the camera - //to the viewport size so that the back buffer 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 + * 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 Filter to modify (not null) - * @param enabled true to enable, false to disable + * @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)) { @@ -392,26 +486,27 @@ 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 viewport 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); } } @@ -419,40 +514,56 @@ private void updateLastFilterIndex() { @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 + // Resizing the camera to fit the new viewport and saving original dimensions cam.resize(w, h, true); left = cam.getViewPortLeft(); right = cam.getViewPortRight(); @@ -461,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; } @@ -483,9 +594,11 @@ 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); @@ -494,18 +607,31 @@ public void reshape(ViewPort vp, int w, int h) { filterTexture = msColor; depthTexture = msDepth; } else { + // 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.setDepthTarget(FrameBufferTarget.newTarget(depthFormat)); filterTexture = new Texture2D(width, height, fbFormat); 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); } @@ -513,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(); @@ -530,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) { @@ -551,27 +681,30 @@ public void setAssetManager(AssetManager assetManager) { } /** - * Sets the format to be used for the internal frame buffer's color buffer + * Sets the preferred `Image.Format` to be used for the internal frame buffer's + * color buffer. * - * @param fbFormat the format + * @param fbFormat The desired `Format` for the color buffer. */ public void setFrameBufferFormat(Format fbFormat) { this.fbFormat = fbFormat; } /** - * Sets the format to be used for the internal frame buffer's depth buffer + * Sets the preferred `Image.Format` to be used for the internal frame buffer's + * depth buffer. * - * @param depthFormat the format + * @param depthFormat The desired `Format` for the depth buffer. */ public void setFrameBufferDepthFormat(Format depthFormat) { this.depthFormat = depthFormat; } /** - * Returns the depth format currently used for the internal frame buffer's depth buffer - * - * @return the depth format + * Returns the `Image.Format` currently used for the internal frame buffer's + * depth buffer. + * + * @return The current depth `Format`. */ public Format getFrameBufferDepthFormat() { return depthFormat; @@ -599,43 +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 from 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 filter type - * @param filterType the filter type - * @return a filter assignable from the given type + * @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); @@ -648,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 241401ff15..857e78dbc3 100644 --- a/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java +++ b/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java @@ -91,11 +91,11 @@ 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; - final private AssetManager manager; + private final AssetManager manager; private boolean enabled = true; @@ -106,8 +106,10 @@ public HDRRenderer(AssetManager manager, 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 if (caps.contains(Caps.FloatColorBufferRGBA)) + bufFormat = Format.RGBA16F; else { enabled = false; return; diff --git a/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java b/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java index 6de0655fab..1a7a7be94f 100644 --- a/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java @@ -49,8 +49,8 @@ public class PreDepthProcessor implements SceneProcessor { private RenderManager rm; private ViewPort vp; - final private Material preDepth; - final private RenderState forcedRS; + private final Material preDepth; + private final RenderState forcedRS; public PreDepthProcessor(AssetManager assetManager){ preDepth = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md"); diff --git a/jme3-core/src/main/java/com/jme3/renderer/Camera.java b/jme3-core/src/main/java/com/jme3/renderer/Camera.java index eb7cca3ae4..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ 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; @@ -179,22 +180,22 @@ public enum FrustumIntersect { //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; /** @@ -1017,7 +1018,8 @@ public float getViewPortLeft() { /** * 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; @@ -1036,7 +1038,8 @@ public float getViewPortRight() { /** * 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; @@ -1055,7 +1058,8 @@ public float getViewPortTop() { /** * 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; @@ -1074,7 +1078,8 @@ public float getViewPortBottom() { /** * 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; @@ -1084,10 +1089,10 @@ public void setViewPortBottom(float bottom) { /** * 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; @@ -1283,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(); } @@ -1557,6 +1571,37 @@ public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) { return 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. * @@ -1577,7 +1622,8 @@ 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 + "]"; } 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 961d78ae6f..647d44a9a6 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -227,6 +227,16 @@ public enum Caps { */ FloatTexture, + /** + * Supports rendering on RGB floating point textures + */ + FloatColorBufferRGB, + + /** + * Supports rendering on RGBA floating point textures + */ + FloatColorBufferRGBA, + /** * Supports integer textures. */ @@ -236,6 +246,7 @@ public enum Caps { * Supports floating point FBO color buffers (Format.RGB16F). */ FloatColorBuffer, + /** * Supports floating point depth buffer. @@ -341,6 +352,11 @@ public enum Caps { */ OpenGLES20, + /** + * Supports WebGL + */ + WebGL, + /** * Supports RGB8 / RGBA8 textures. */ 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 7fb60e7322..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-2021 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.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 @@ -49,6 +50,11 @@ public class RenderContext { */ public static final int maxTextureUnits = 16; + /** + * Number of buffer object units that JME supports. + */ + public static final int maxBufferObjectUnits = 8; + /** * Criteria for culling faces. * @@ -256,6 +262,15 @@ public class RenderContext { public final WeakReference boundTextures[] = new WeakReference[maxTextureUnits]; + + /** + * 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 final WeakReference[] boundBO = new WeakReference[maxBufferObjectUnits]; + /** * IDList for texture units. * 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 a1b453a096..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-2021 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; @@ -44,7 +48,6 @@ import com.jme3.post.SceneProcessor; import com.jme3.profile.AppProfiler; import com.jme3.profile.AppStep; -import com.jme3.profile.SpStep; import com.jme3.profile.VpStep; import com.jme3.renderer.queue.GeometryList; import com.jme3.renderer.queue.RenderQueue; @@ -58,42 +61,46 @@ 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; /** - * 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; - private int viewY; - private int viewWidth; - private int viewHeight; + private final SafeArrayList forcedOverrides = new SafeArrayList<>(MatParamOverride.class); + private final Matrix4f orthoMatrix = new Matrix4f(); private final LightList filteredLightList = new LightList(null); private boolean handleTranslucentBucket = true; @@ -101,16 +108,131 @@ public class RenderManager { 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; /** * Creates a high-level rendering interface over the * low-level rendering interface. * - * @param renderer (alias created) + * @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; } /** @@ -396,7 +518,7 @@ public void notifyRescale(float x, float y) { for (ViewPort vp : preViewPorts) { notifyRescale(vp, x, y); } - for (ViewPort vp : viewPorts) { + for (ViewPort vp : viewPorts) { notifyRescale(vp, x, y); } for (ViewPort vp : postViewPorts) { @@ -405,16 +527,22 @@ public void notifyRescale(float x, float y) { } /** - * Sets 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. + * 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; + } + + /** + * Gets the forced material that overrides materials set on geometries. * - * @param mat The forced material to set, or null to return to normal + * @return The forced {@link Material}, or null if no material is forced. */ - public void setForcedMaterial(Material mat) { - forcedMaterial = mat; + public Material getForcedMaterial() { + return forcedMaterial; } /** @@ -462,10 +590,9 @@ public void setAppProfiler(AppProfiler prof) { } /** - * Returns the forced technique name set. - * - * @return the forced technique name set. + * 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() { @@ -481,9 +608,7 @@ public String getForcedTechnique() { * 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. - * + * @param forcedTechnique The technique to force, or null to disable forcing. * @see #renderGeometry(com.jme3.scene.Geometry) */ public void setForcedTechnique(String forcedTechnique) { @@ -492,13 +617,12 @@ public void setForcedTechnique(String forcedTechnique) { /** * Adds a forced material parameter to use when rendering geometries. - * - *

    The provided parameter takes precedence over parameters set on the + *

    + * The provided parameter takes precedence over parameters set on the * 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) { @@ -506,9 +630,9 @@ public void addForcedMatParam(MatParamOverride override) { } /** - * Removes 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) { @@ -622,22 +746,49 @@ public void updateUniformBindings(Shader shader) { * @see com.jme3.material.Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) */ public void renderGeometry(Geometry geom) { - this.renderer.pushDebugGroup(geom.getName()); - if (geom.isIgnoreTransform()) { - setWorldMatrix(Matrix4f.IDENTITY); - } else { - 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 exists, we try to force it for the render. @@ -658,8 +809,7 @@ public void renderGeometry(Geometry geom) { RenderState tmpRs = forcedRenderState; if (geom.getMaterial().getActiveTechnique().getDef().getForcedRenderState() != null) { //forcing forced technique renderState - forcedRenderState - = geom.getMaterial().getActiveTechnique().getDef().getForcedRenderState(); + forcedRenderState = geom.getMaterial().getActiveTechnique().getDef().getForcedRenderState(); } // use geometry's material material.render(geom, lightList, this); @@ -723,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()); @@ -773,10 +923,10 @@ public void preloadScene(Spatial scene) { * contain the flattened scene graph. */ public void renderScene(Spatial scene, ViewPort vp) { - //reset of the camera plane state for proper culling - //(must be 0 for the first note of the scene to be rendered) + // reset of the camera plane state for proper culling + // (must be 0 for the first note of the scene to be rendered) vp.getCamera().setPlaneState(0); - //rendering the scene + // queue the scene for rendering renderSubScene(scene, vp); } @@ -787,12 +937,10 @@ public void renderScene(Spatial scene, ViewPort vp) { * @param vp the ViewPort to render in (not null) */ private void renderSubScene(Spatial scene, ViewPort vp) { - - // check culling first. + // check culling first if (!scene.checkCulling(vp.getCamera())) { return; } - scene.runControlRender(this, vp); if (scene instanceof Node) { // Recurse for all children @@ -806,12 +954,11 @@ private void renderSubScene(Spatial scene, ViewPort vp) { renderSubScene(children.get(i), vp); } } else if (scene instanceof Geometry) { - // add to the render queue + // addUserEvent to the render queue Geometry gm = (Geometry) scene; if (gm.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); } - vp.getQueue().addToQueue(gm, scene.getQueueBucket()); } } @@ -922,10 +1069,9 @@ public int getSinglePassLightBatchSize() { */ 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); } - /** * Renders the given viewport queues. * @@ -957,9 +1103,7 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { if (prof != null) { prof.vpStep(VpStep.RenderBucket, vp, Bucket.Opaque); } - this.renderer.pushDebugGroup(Bucket.Opaque.name()); rq.renderQueue(Bucket.Opaque, this, cam, flush); - this.renderer.popDebugGroup(); // render the sky, with depth range set to the farthest if (!rq.isQueueEmpty(Bucket.Sky)) { @@ -967,13 +1111,10 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { prof.vpStep(VpStep.RenderBucket, vp, Bucket.Sky); } renderer.setDepthRange(1, 1); - this.renderer.pushDebugGroup(Bucket.Sky.name()); rq.renderQueue(Bucket.Sky, this, cam, flush); - this.renderer.popDebugGroup(); depthRangeChanged = true; } - // transparent objects are last because they require blending with the // rest of the scene's objects. Consequently, they are sorted // back-to-front. @@ -985,9 +1126,7 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { renderer.setDepthRange(0, 1); depthRangeChanged = false; } - this.renderer.pushDebugGroup(Bucket.Transparent.name()); rq.renderQueue(Bucket.Transparent, this, cam, flush); - this.renderer.popDebugGroup(); } if (!rq.isQueueEmpty(Bucket.Gui)) { @@ -996,9 +1135,7 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { } renderer.setDepthRange(0, 0); setCamera(cam, true); - this.renderer.pushDebugGroup(Bucket.Gui.name()); rq.renderQueue(Bucket.Gui, this, cam, flush); - this.renderer.popDebugGroup(); setCamera(cam, false); depthRangeChanged = true; } @@ -1033,14 +1170,14 @@ public void renderTranslucentQueue(ViewPort vp) { } private void setViewPort(Camera cam) { - // this will make sure to update viewport only if needed + // this will make sure to clearReservations viewport only if needed if (cam != prevCam || cam.isViewportChanged()) { - viewX = (int) (cam.getViewPortLeft() * cam.getWidth()); - viewY = (int) (cam.getViewPortBottom() * cam.getHeight()); + 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); @@ -1115,135 +1252,56 @@ public void renderViewPortRaw(ViewPort vp) { } /** - * Renders the {@link ViewPort}. + * Applies the ViewPort's Camera and FrameBuffer in preparation + * for rendering. * - *

    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 + * @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); + /** + * 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; } - List scenes = vp.getScenes(); - for (int i = scenes.size() - 1; i >= 0; i--) { - renderScene(scenes.get(i), vp); + RenderPipeline pipeline = vp.getPipeline(); + if (pipeline == null) { + pipeline = defaultPipeline; } - 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()); - } + PipelineContext context = pipeline.fetchPipelineContext(this); + if (context == null) { + throw new NullPointerException("Failed to fetch pipeline context."); } - - if (prof != null) { - prof.vpStep(VpStep.FlushQueue, vp, null); + if (!context.startViewPortRender(this, vp)) { + usedContexts.add(context); } - flushQueue(vp); - - if (processors != null) { - if (prof != null) { - prof.vpStep(VpStep.PostFrame, vp, null); - } - for (SceneProcessor proc : processors.getArray()) { - if (prof != null) { - prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName()); - } - proc.postFrame(vp.getOutputFrameBuffer()); - } - if (prof != null) { - prof.vpStep(VpStep.ProcEndRender, vp, null); - } + if (!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); } /** @@ -1297,5 +1355,63 @@ public void render(float tpf, boolean 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 753bf2f1a6..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-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,15 +33,17 @@ 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; @@ -273,6 +275,15 @@ public interface Renderer { */ 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 setTextureImage(int unit, TextureImage tex) throws TextureUnitException; /** * Modifies the given Texture with the given Image. @@ -306,8 +317,15 @@ public void setTexture(int unit, Texture tex) * * @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. * @@ -527,4 +545,19 @@ 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 dadd3c1fc7..205ca64de0 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RendererException.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RendererException.java @@ -47,4 +47,7 @@ public class RendererException extends RuntimeException { public RendererException(String message) { super(message); } + public RendererException(Exception e) { + super(e); + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java index 6f0424fb30..f256405276 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java +++ b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ */ package com.jme3.renderer; +import com.jme3.renderer.pipeline.RenderPipeline; import com.jme3.math.ColorRGBA; import com.jme3.post.SceneProcessor; import com.jme3.renderer.queue.RenderQueue; @@ -84,6 +85,10 @@ public class ViewPort { * Scene processors currently applied. */ protected final SafeArrayList processors = new SafeArrayList<>(SceneProcessor.class); + /** + * Dedicated pipeline. + */ + protected RenderPipeline pipeline; /** * FrameBuffer for output. */ @@ -424,5 +429,28 @@ public void setEnabled(boolean enable) { public boolean isEnabled() { return enabled; } + + /** + * Sets the pipeline used by this viewport for rendering. + *

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

    + * default=null + * + * @param pipeline pipeline, or null to use render manager's pipeline + */ + public void setPipeline(RenderPipeline pipeline) { + this.pipeline = pipeline; + } + + /** + * Gets the framegraph used by this viewport for rendering. + * + * @return + */ + public RenderPipeline getPipeline() { + return pipeline; + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/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 ff23e8a02d..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 @@ -68,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; @@ -148,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; @@ -420,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

    *

    @@ -820,6 +829,11 @@ 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

    * 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 89fea8c88c..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 @@ -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

    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 04ad52b2dd..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 @@ -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. */ @@ -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/GLES_30.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLES_30.java index 42c59d940b..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 @@ -31,6 +31,8 @@ */ package com.jme3.renderer.opengl; +import java.nio.IntBuffer; + /** * GL functions and constants only available on vanilla OpenGL ES 3.0. * @@ -40,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 ea9add1583..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 @@ -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; 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 2532f5837e..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); @@ -242,13 +242,17 @@ public static GLImageFormat[][] getFormatsForCaps(EnumSet caps) { // 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)) { @@ -274,11 +278,20 @@ 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); 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 82bcca3a4f..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-2023 jMonkeyEngine + * Copyright (c) 2009-2026 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,6 +50,10 @@ 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; @@ -57,11 +61,14 @@ 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; @@ -131,6 +138,7 @@ public void setGenerateMipmapsForFrameBuffer(boolean v) { generateMipmapsForFramebuffers = v; } + public void setDebugEnabled(boolean v) { debug = v; } @@ -187,7 +195,12 @@ 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)); @@ -208,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); @@ -378,7 +395,7 @@ 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); } } @@ -409,9 +426,14 @@ private void loadCapabilitiesCommon() { } if (hasExtension("GL_ARB_color_buffer_float") && - hasExtension("GL_ARB_half_float_pixel")) { + 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")) { @@ -450,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 " @@ -495,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)); @@ -530,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); } @@ -539,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")) { @@ -600,7 +626,7 @@ private void loadCapabilitiesCommon() { caps.add(Caps.UnpackRowLength); } - if (caps.contains(Caps.OpenGL43) || hasExtension("GL_KHR_debug")) { + if (caps.contains(Caps.OpenGL43) || hasExtension("GL_KHR_debug") || caps.contains(Caps.WebGL)) { caps.add(Caps.GLDebug); } @@ -679,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); @@ -1247,6 +1281,37 @@ protected void updateUniformLocation(Shader shader, Uniform uniform) { } } + 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(); @@ -1282,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; @@ -1311,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; @@ -1325,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: @@ -1348,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()); } } @@ -1364,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) { @@ -1462,7 +1570,7 @@ 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()); + if(source.getName()!=null) glext.glObjectLabel(GLExt.GL_SHADER, id, source.getName()); } } else { throw new RendererException("Cannot recompile shader source"); @@ -1477,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); @@ -1508,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"); } @@ -1526,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 directive 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()); @@ -1668,7 +1749,7 @@ public void updateShaderData(Shader shader) { public void setShader(Shader shader) { if (shader == null) { throw new IllegalArgumentException("Shader cannot be null"); - } else { + } else { if (shader.isUpdateNeeded()) { updateShaderData(shader); } @@ -2029,8 +2110,17 @@ 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; } @@ -2137,7 +2227,7 @@ public void setFrameBuffer(FrameBuffer fb) { context.boundFB = fb; if (debug && caps.contains(Caps.GLDebug)) { - if (fb.getName() != null) glext.glObjectLabel(GL3.GL_FRAMEBUFFER, fb.getId(), fb.getName()); + if (fb.getName() != null) glext.glObjectLabel(GL3.GL_FRAMEBUFFER, fb.getId(), fb.getName()); } } } @@ -2635,7 +2725,7 @@ 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 @@ -2666,7 +2756,56 @@ public void setTexture(int unit, Texture tex) throws TextureUnitException { 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) @@ -2834,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 @@ -2863,26 +3042,29 @@ 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(); } @@ -3311,19 +3493,11 @@ public void setMainFrameBufferSrgb(boolean enableSrgb) { setFrameBuffer(null); if (enableSrgb) { - if ( - // Workaround: getBoolean(GLExt.GL_FRAMEBUFFER_SRGB_CAPABLE_EXT) causes error 1280 (invalid enum) on macos - JmeSystem.getPlatform().getOs() != Platform.Os.MacOS - && !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)"); } } @@ -3424,4 +3598,22 @@ public boolean isMainFrameBufferSrgb() { 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/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 11ce437e27..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-2021 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()); 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 5836c87614..546e766835 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java @@ -51,7 +51,7 @@ public class GeometryList implements Iterable{ private static final int DEFAULT_SIZE = 32; private Geometry[] geometries; - final private ListSort listSort; + private final ListSort listSort; private int size; private GeometryComparator comparator; diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java index e95463f14e..a16dc18665 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java @@ -264,7 +264,7 @@ public void addToQueue(Geometry g, Bucket bucket) { } } - private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean flush) { list.setCamera(cam); // select camera for sorting list.sort(); for (int i = 0; i < list.size(); i++) { @@ -273,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) { @@ -304,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); @@ -324,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/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index 42034ea047..aa5d62a02a 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -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. @@ -188,6 +187,11 @@ protected void doBatch() { 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(); @@ -244,15 +244,9 @@ protected void doBatch() { 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(); } } @@ -291,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"); @@ -387,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: @@ -617,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; 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 43b0f46d81..dd3da84c9e 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -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; @@ -136,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()) { @@ -171,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"); } @@ -227,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(); } @@ -257,6 +272,7 @@ public Mesh getMesh() { */ @Override public void setMaterial(Material material) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); this.material = material; nbSimultaneousGPUMorph = -1; if (isGrouped()) { 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 fb5a6f7dfb..2819c2838f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -129,6 +129,7 @@ public enum Mode { * for each patch (default is 3 for triangle tessellation) */ Patch(true); + private boolean listMode = false; private Mode(boolean listMode) { @@ -148,28 +149,44 @@ 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 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; @@ -199,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,7 +243,7 @@ public Mesh deepClone() { // 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); @@ -236,7 +253,7 @@ public Mesh deepClone() { clone.buffersList.add(bufClone); } - clone.vertexArrayID = -1; + clone.vertexArrayID = DEFAULT_VERTEX_ARRAY_ID; clone.vertCount = vertCount; clone.elementCount = elementCount; clone.instanceCount = instanceCount; @@ -296,7 +313,7 @@ public Mesh cloneForAnim() { public Mesh jmeClone() { try { Mesh clone = (Mesh) super.clone(); - clone.vertexArrayID = -1; + clone.vertexArrayID = DEFAULT_VERTEX_ARRAY_ID; return clone; } catch (CloneNotSupportedException ex) { throw new AssertionError(); @@ -309,7 +326,7 @@ public Mesh jmeClone() { @Override 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); @@ -616,7 +633,7 @@ public void setMaxNumWeights(int maxNumWeights) { */ @Deprecated public float getPointSize() { - return 1.0f; + return DEFAULT_POINT_SIZE; } /** @@ -969,7 +986,7 @@ public int getId() { * @param id the array ID */ public void setId(int id) { - if (vertexArrayID != -1) { + if (vertexArrayID != DEFAULT_VERTEX_ARRAY_ID) { throw new IllegalStateException("ID has already been set."); } @@ -995,7 +1012,7 @@ public void createCollisionData() { * generated BIHTree. */ public void clearCollisionData() { - collisionTree = null; + collisionTree = DEFAULT_COLLISION_TREE; } /** @@ -1620,15 +1637,15 @@ 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; @@ -1663,17 +1680,17 @@ public void write(JmeExporter ex) throws IOException { 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); 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 0424cea053..46bb0829e8 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -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; @@ -201,6 +202,7 @@ private void addUpdateChildren(SafeArrayList results) { * that would change state. */ void invalidateUpdateList() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); updateListValid = false; if (parent != null) { parent.invalidateUpdateList(); @@ -344,6 +346,7 @@ public int attachChild(Spatial child) { * @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"); } @@ -428,6 +431,7 @@ public int detachChildNamed(String childName) { * @return the child at the supplied index. */ public Spatial detachChildAt(int index) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); Spatial child = children.remove(index); if (child != null) { child.setParent(null); @@ -455,6 +459,7 @@ public Spatial detachChildAt(int index) { * 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. @@ -483,6 +488,7 @@ public int getChildIndex(Spatial sp) { * @param index2 The index of the second child to swap */ public void swapChildren(int index1, int index2) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); Spatial c2 = children.get(index2); Spatial c1 = children.remove(index1); children.add(index1, c2); @@ -562,6 +568,7 @@ public List getChildren() { @Override public void setMaterial(Material mat) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); for (int i = 0; i < children.size(); i++) { children.get(i).setMaterial(mat); } @@ -778,6 +785,7 @@ public void read(JmeImporter importer) throws IOException { @Override public void setModelBound(BoundingVolume modelBound) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); if (children != null) { for (Spatial child : children.getArray()) { child.setModelBound(modelBound != null ? modelBound.clone(null) : null); @@ -787,6 +795,7 @@ public void setModelBound(BoundingVolume modelBound) { @Override public void updateModelBound() { + 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 2375d4ae37..426c636079 100644 --- a/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java @@ -39,7 +39,9 @@ * SimpleBatchNode comes with some restrictions, but can yield better performances. * Geometries to be batched has to be attached directly to the BatchNode * You can't attach a Node to a SimpleBatchNode - * SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure. + * SimpleBatchNode is recommended when you have a large number of geometries using the + * same material that does not require a complex scene graph structure. + * * @see BatchNode * @author Nehon */ @@ -55,9 +57,9 @@ public SimpleBatchNode(String name) { @Override public int attachChild(Spatial child) { - if (!(child instanceof Geometry)) { - throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child of type Geometry, use BatchMode.Complex to use a complex structure"); + throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child " + + "of type Geometry, use BatchMode.Complex to use a complex structure"); } return super.attachChild(child); @@ -71,7 +73,8 @@ protected void setTransformRefresh() { batch.geometry.setTransformRefresh(); } } - final private Matrix4f cachedLocalMat = new Matrix4f(); + + private final Matrix4f cachedLocalMat = new Matrix4f(); @Override protected Matrix4f getTransformMatrix(Geometry g){ @@ -89,7 +92,6 @@ protected Matrix4f getTransformMatrix(Geometry g){ return cachedLocalMat; } - @Override public void batch() { doBatch(); diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index 069da795fa..f94ed817d2 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -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; @@ -278,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. @@ -299,6 +302,7 @@ protected void setLightListRefresh() { } protected void setMatParamOverrideRefresh() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); refreshFlags |= RF_MATPARAM_OVERRIDE; Spatial p = parent; while (p != null) { @@ -316,6 +320,7 @@ protected void setMatParamOverrideRefresh() { * a refresh is required. */ protected void setBoundRefresh() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); refreshFlags |= RF_BOUND; Spatial p = parent; @@ -364,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; @@ -586,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; } @@ -599,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); } @@ -612,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"); } @@ -626,6 +633,7 @@ public void addMatParamOverride(MatParamOverride override) { * @see MatParamOverride */ public void removeMatParamOverride(MatParamOverride override) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); if (localOverrides.remove(override)) { setMatParamOverrideRefresh(); } @@ -637,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(); } @@ -653,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; @@ -772,6 +781,7 @@ public void runControlRender(RenderManager rm, ViewPort vp) { * @see Spatial#removeControl(java.lang.Class) */ public void addControl(Control control) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); boolean before = requiresUpdates(); controls.add(control); control.setSpatial(this); @@ -811,7 +821,7 @@ public void addControlAt(int index, Control control) { if (index < numControls) { // re-arrange the list directly boolean success = controls.remove(control); - assert success; + assert success : "Surprising control remove failure. " + control.getClass().getSimpleName() + " from spatial " + getName(); controls.add(index, control); } } @@ -823,6 +833,7 @@ public void addControlAt(int index, Control control) { * @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())) { @@ -850,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) { @@ -952,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(); } /** @@ -986,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. @@ -1005,6 +1039,7 @@ public Node getParent() { * the parent of this node. */ protected void setParent(Node parent) { + assert SceneGraphThreadWarden.updateRequirement(this, parent); this.parent = parent; } @@ -1063,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); @@ -1152,6 +1187,9 @@ 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) { @@ -1366,6 +1404,7 @@ public RenderQueue.ShadowMode getShadowMode() { * @param lod The lod level to set. */ public void setLodLevel(int lod) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); } /** 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 a7b814fe11..2fb04f44d3 100644 --- a/jme3-core/src/main/java/com/jme3/scene/UserData.java +++ b/jme3-core/src/main/java/com/jme3/scene/UserData.java @@ -138,7 +138,7 @@ public static byte getObjectType(Object type) { 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; 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 8698bf421a..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ 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) */ @@ -233,7 +233,7 @@ 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. */ @@ -261,7 +261,7 @@ 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. */ @@ -1116,7 +1116,7 @@ public NativeObject createDestructableClone() { @Override public long getUniqueId() { - return ((long) OBJTYPE_VERTEXBUFFER << 32) | ((long) id); + return ((long) OBJTYPE_VERTEXBUFFER << 32) | (0xffffffffL & (long) id); } @Override @@ -1130,6 +1130,7 @@ 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(); @@ -1166,6 +1167,8 @@ 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(); @@ -1191,14 +1194,28 @@ public void read(JmeImporter im) throws IOException { } } + /** + * 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) { - name = getClass().getSimpleName() + "(" + getBufferType().name() + ")"; + 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/AreaUtils.java b/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java index 52f388c7df..0097191ca8 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java @@ -31,10 +31,7 @@ */ package com.jme3.scene.control; -import com.jme3.bounding.BoundingBox; -import com.jme3.bounding.BoundingSphere; import com.jme3.bounding.BoundingVolume; -import com.jme3.math.FastMath; /** * AreaUtils is used to calculate the area of various objects, such as bounding volumes. These diff --git a/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java b/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java index c7a1ae4df8..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,52 +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. + * `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 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 } + /** + * 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 SpatialToCamera or CameraToSpatial + * @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; } @@ -104,6 +129,7 @@ public Light getLight() { } public void setLight(Light light) { + validateSupportedLightType(light); this.light = light; } @@ -115,86 +141,141 @@ public void setControlDir(ControlDirection controlDir) { this.controlDir = controlDir; } - // fields used when inverting 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; } } /** - * Sets the light to adopt the spatial's world transformations. + * Updates the light's position and/or direction to match the spatial's + * world transformation. * - * @author Markil 3 - * @author pspeed42 + * @param light The light whose properties will be set. */ private void spatialToLight(Light light) { TempVars vars = TempVars.get(); - final Vector3f worldTranslation = vars.vect1; - worldTranslation.set(spatial.getWorldTranslation()); - final Vector3f worldDirection = vars.vect2; - spatial.getWorldRotation().mult(Vector3f.UNIT_Z, worldDirection).negateLocal(); + final Vector3f worldPosition = vars.vect1; + worldPosition.set(spatial.getWorldTranslation()); + + final Vector3f lightDirection = vars.vect2; + spatial.getWorldRotation().getRotationColumn(axisRotation.ordinal(), lightDirection); + if (invertAxisDirection) { + lightDirection.negateLocal(); + } if (light instanceof PointLight) { - ((PointLight) light).setPosition(worldTranslation); + ((PointLight) light).setPosition(worldPosition); + } else if (light instanceof DirectionalLight) { - ((DirectionalLight) light).setDirection(worldDirection); + ((DirectionalLight) light).setDirection(lightDirection); + } else if (light instanceof SpotLight) { - final SpotLight spotLight = (SpotLight) light; - spotLight.setPosition(worldTranslation); - spotLight.setDirection(worldDirection); + SpotLight sl = (SpotLight) light; + sl.setPosition(worldPosition); + sl.setDirection(lightDirection); } vars.release(); } /** - * Sets the spatial to adopt the light's world transformations. + * Updates the spatial's local transformation (position and/or rotation) + * to match the light's world transformation. * - * @author Markil 3 + * @param light The light from which properties will be read. */ private void lightToSpatial(Light light) { TempVars vars = TempVars.get(); - Vector3f translation = vars.vect1; - Vector3f direction = vars.vect2; + Vector3f lightPosition = vars.vect1; + Vector3f lightDirection = vars.vect2; Quaternion rotation = vars.quat1; - boolean rotateSpatial = false, translateSpatial = false; + boolean rotateSpatial = false; + boolean translateSpatial = false; if (light instanceof PointLight) { - PointLight pLight = (PointLight) light; - translation.set(pLight.getPosition()); + PointLight pl = (PointLight) light; + lightPosition.set(pl.getPosition()); translateSpatial = true; + } else if (light instanceof DirectionalLight) { - DirectionalLight dLight = (DirectionalLight) light; - direction.set(dLight.getDirection()).negateLocal(); + DirectionalLight dl = (DirectionalLight) light; + lightDirection.set(dl.getDirection()); + if (invertAxisDirection) { + lightDirection.negateLocal(); + } rotateSpatial = true; + } else if (light instanceof SpotLight) { - SpotLight sLight = (SpotLight) light; - translation.set(sLight.getPosition()); - direction.set(sLight.getDirection()).negateLocal(); - translateSpatial = rotateSpatial = true; + SpotLight sl = (SpotLight) light; + lightPosition.set(sl.getPosition()); + lightDirection.set(sl.getDirection()); + if (invertAxisDirection) { + lightDirection.negateLocal(); + } + translateSpatial = true; + rotateSpatial = true; } + + // 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(translation); - vars.tempMat4.translateVect(translation); - vars.tempMat4.rotateVect(direction); + vars.tempMat4.rotateVect(lightPosition); + vars.tempMat4.translateVect(lightPosition); + vars.tempMat4.rotateVect(lightDirection); } + // Apply transformed properties to spatial's local transformation if (rotateSpatial) { - rotation.lookAt(direction, Vector3f.UNIT_Y).normalizeLocal(); + rotation.lookAt(lightDirection, Vector3f.UNIT_Y).normalizeLocal(); spatial.setLocalRotation(rotation); } if (translateSpatial) { - spatial.setLocalTranslation(translation); + spatial.setLocalTranslation(lightPosition); } vars.release(); } @@ -214,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/debug/Arrow.java b/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java index 05f01e4367..8f46298693 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java @@ -47,8 +47,8 @@ */ public class Arrow extends Mesh { - final private Quaternion tempQuat = new Quaternion(); - final private Vector3f tempVec = new Vector3f(); + private final Quaternion tempQuat = new Quaternion(); + private final Vector3f tempVec = new Vector3f(); private static final float[] positions = new float[]{ 0, 0, 0, diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java index 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/WireFrustum.java b/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java index 7b3114bba5..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,74 +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 { /** - * This constructor is for serialization only. Do not use. + * For Serialization only. Do not use. */ protected WireFrustum() { } - public WireFrustum(Vector3f[] points){ - initGeom(this, points); - } - - 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(); - vb.updateData(a); - + currBuff.clear(); // Clear + currBuff.put(newBuff); // Copy + currBuff.rewind(); // Rewind + + // Update the VertexBuffer with the modified FloatBuffer data + vb.updateData(currBuff); + + // 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 3fa4f48637..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-2020 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; @@ -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 6b14b49811..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,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,59 +31,115 @@ */ 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 @@ -102,139 +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); + + Spatial target = sp; - ad = new ArmatureDebugger(forSpatial.getName() + "_Armature", armature, visitor.deformingJoints); - ad.setLocalTransform(forSpatial.getWorldTransform()); - if (forSpatial instanceof Node) { + 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); } } } - final 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(); - - Camera camera = app.getCamera(); - Vector3f click3d = camera.getWorldCoordinates(click2d, 0f, tmp); - Vector3f dir = camera.getWorldCoordinates(click2d, 1f, tmp2) - .subtractLocal(click3d) - .normalizeLocal(); - 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 d7973eabc1..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-2021 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,104 +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 deformingJoints a list of joints + * @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(1f); + // 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(1f); + // 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; } @@ -168,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 612d6c9551..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-2021 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; @@ -46,20 +44,38 @@ * @author Marcin Roguski (Kaelthas) */ public class ArmatureInterJointsWire extends Mesh { - final 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); } /** - * For serialization only. Do not use. + * 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 ArmatureInterJointsWire() { - } - protected void updateGeometry(Vector3f start, Vector3f[] ends) { float[] pos = new float[ends.length * 3 + 3]; pos[0] = start.x; @@ -78,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); diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java index 75acec75f8..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-2021 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. */ - final 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. */ - final private Map jointToGeoms = new HashMap<>(); - final private Map geomToJoint = new HashMap<>(); private Joint selectedJoint = null; - final private Vector3f tmp = new Vector3f(); - final private Vector2f tmpv2 = new Vector2f(); - private final static ColorRGBA selectedColor = ColorRGBA.Orange; - private final static ColorRGBA selectedColorJ = ColorRGBA.Yellow; - private final static ColorRGBA outlineColor = ColorRGBA.LightGray; - private final static ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); + // 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; @@ -88,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); @@ -134,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); @@ -148,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; } @@ -165,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) { @@ -232,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); @@ -305,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/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java index 2f76a01861..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-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -276,7 +276,14 @@ private void swap(int idx1, int idx2) { } } + /** + * @deprecated use {@link #updateInstances(com.jme3.renderer.Camera)} + */ public void updateInstances() { + updateInstances(cam); + } + + public void updateInstances(Camera cam) { FloatBuffer fb = (FloatBuffer) transformInstanceData.getData(); fb.limit(fb.capacity()); fb.position(0); diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java index ea5b576ed3..821264f9a6 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2021 jMonkeyEngine + * Copyright (c) 2014-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,6 +35,7 @@ import com.jme3.export.JmeImporter; import com.jme3.material.MatParam; import com.jme3.material.Material; +import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; @@ -161,7 +162,7 @@ public void update(float tpf){ @Override public void render(RenderManager rm, ViewPort vp) { - node.renderFromControl(); + node.renderFromControl(vp.getCamera()); } @Override @@ -198,9 +199,9 @@ public InstancedNode(String name) { addControl(control); } - private void renderFromControl() { + private void renderFromControl(Camera cam) { for (InstancedGeometry ig : instancesMap.values()) { - ig.updateInstances(); + ig.updateInstances(cam); } } diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java index ba3361c928..f7f66a2b04 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java @@ -43,7 +43,7 @@ */ public class IndexByteBuffer extends IndexBuffer { - final private ByteBuffer buf; + private final ByteBuffer buf; /** * the largest index value that can be put to the buffer */ diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java index aeec327e16..c8d691589f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java @@ -43,7 +43,7 @@ */ public class IndexIntBuffer extends IndexBuffer { - final private IntBuffer buf; + private final IntBuffer buf; public IndexIntBuffer(IntBuffer buffer) { buf = buffer; diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java index 6fd3253c6a..c505947eb9 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java @@ -43,7 +43,7 @@ */ public class IndexShortBuffer extends IndexBuffer { - final private ShortBuffer buf; + private final ShortBuffer buf; /** * the largest index value that can be put to the buffer */ diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java b/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java index 3299de5ce2..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 { - final 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/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 f1522ff5ba..0000000000 --- a/jme3-core/src/main/java/com/jme3/shader/BufferObject.java +++ /dev/null @@ -1,834 +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. - */ - @SuppressWarnings("unchecked") - 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 byte count to present the value on the GPU. - * - * @param value the value. - * @param multiplier the multiplier. - * @return the estimated byte count. - */ - 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 byte count to present the values on the GPU. - * - * @param values the values. - * @return the estimated byte count. - */ - protected int estimate(final float[] values) { - return values.length * 4; - } - - /** - * Estimates byte count to present the values on the GPU. - * - * @param values the values. - * @return the estimated byte count. - */ - 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. - */ - @SuppressWarnings("unchecked") - 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. - */ - @SuppressWarnings("unchecked") - 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. - */ - @SuppressWarnings("unchecked") - 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. - */ - @SuppressWarnings("unchecked") - 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. - */ - @SuppressWarnings("unchecked") - 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 fbd1350eb8..425f0ad0bc 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java @@ -50,7 +50,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { /** * the indentation characters 1à tabulation characters */ - private final static String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + private static final String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; protected ShaderNodeVariable inPosTmp; diff --git a/jme3-core/src/main/java/com/jme3/shader/Shader.java b/jme3-core/src/main/java/com/jme3/shader/Shader.java index 7860e29b13..eb2f778d39 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Shader.java +++ b/jme3-core/src/main/java/com/jme3/shader/Shader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -189,7 +189,7 @@ public String getDefines() { @Override public long getUniqueId() { - return ((long)OBJTYPE_SHADERSOURCE << 32) | ((long)id); + return ((long)OBJTYPE_SHADERSOURCE << 32) | (0xffffffffL & (long)id); } @Override @@ -462,6 +462,6 @@ public NativeObject createDestructableClone(){ @Override public long getUniqueId() { - return ((long)OBJTYPE_SHADER << 32) | ((long)id); + return ((long)OBJTYPE_SHADER << 32) | (0xffffffffL & (long)id); } } diff --git a/jme3-core/src/main/java/com/jme3/shader/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/ShaderNodeDefinition.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java index fce3369a99..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-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -201,7 +201,7 @@ public void write(JmeExporter ex) throws IOException { 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() { 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 45006a5a6c..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-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ */ package com.jme3.shader; +import com.jme3.material.Material.BindUnits; import com.jme3.math.*; import com.jme3.util.BufferUtils; import com.jme3.util.TempVars; @@ -197,7 +198,8 @@ public void clearValue(){ } } - public void setValue(VarType type, Object value){ + public void setValue(VarType type, Object value) { + assert !(value instanceof BindUnits); if (location == LOC_NOT_DEFINED) { return; } diff --git a/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java index 1a6ab75ca6..8d55ab4430 100644 --- a/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java +++ b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java @@ -55,31 +55,31 @@ public class UniformBindingManager { private float near, far; private Float time, tpf; private int viewX, viewY, viewWidth, viewHeight; - final private Vector3f camUp = new Vector3f(), + private final Vector3f camUp = new Vector3f(), camLeft = new Vector3f(), camDir = new Vector3f(), camLoc = new Vector3f(); - final private Matrix4f tempMatrix = new Matrix4f(); - final private Matrix4f viewMatrix = new Matrix4f(); - final private Matrix4f projMatrix = new Matrix4f(); - final private Matrix4f viewProjMatrix = new Matrix4f(); - final private Matrix4f worldMatrix = new Matrix4f(); - final private Matrix4f worldViewMatrix = new Matrix4f(); - final private Matrix4f worldViewProjMatrix = new Matrix4f(); - final private Matrix3f normalMatrix = new Matrix3f(); - final private Matrix3f worldNormalMatrix = new Matrix3f(); - final private Matrix4f worldMatrixInv = new Matrix4f(); - final private Matrix3f worldMatrixInvTrsp = new Matrix3f(); - final private Matrix4f viewMatrixInv = new Matrix4f(); - final private Matrix4f projMatrixInv = new Matrix4f(); - final private Matrix4f viewProjMatrixInv = new Matrix4f(); - final private Matrix4f worldViewMatrixInv = new Matrix4f(); - final private Matrix3f normalMatrixInv = new Matrix3f(); - final private Matrix4f worldViewProjMatrixInv = new Matrix4f(); - final private Vector4f viewPort = new Vector4f(); - final private Vector2f resolution = new Vector2f(); - final private Vector2f resolutionInv = new Vector2f(); - final private Vector2f nearFar = new Vector2f(); + private final Matrix4f tempMatrix = new Matrix4f(); + private final Matrix4f viewMatrix = new Matrix4f(); + private final Matrix4f projMatrix = new Matrix4f(); + private final Matrix4f viewProjMatrix = new Matrix4f(); + private final Matrix4f worldMatrix = new Matrix4f(); + private final Matrix4f worldViewMatrix = new Matrix4f(); + private final Matrix4f worldViewProjMatrix = new Matrix4f(); + private final Matrix3f normalMatrix = new Matrix3f(); + private final Matrix3f worldNormalMatrix = new Matrix3f(); + private final Matrix4f worldMatrixInv = new Matrix4f(); + private final Matrix3f worldMatrixInvTrsp = new Matrix3f(); + private final Matrix4f viewMatrixInv = new Matrix4f(); + private final Matrix4f projMatrixInv = new Matrix4f(); + private final Matrix4f viewProjMatrixInv = new Matrix4f(); + private final Matrix4f worldViewMatrixInv = new Matrix4f(); + private final Matrix3f normalMatrixInv = new Matrix3f(); + private final Matrix4f worldViewProjMatrixInv = new Matrix4f(); + private final Vector4f viewPort = new Vector4f(); + private final Vector2f resolution = new Vector2f(); + private final Vector2f resolutionInv = new Vector2f(); + private final Vector2f nearFar = new Vector2f(); /** * Internal use only. diff --git a/jme3-core/src/main/java/com/jme3/shader/VarType.java b/jme3-core/src/main/java/com/jme3/shader/VarType.java index 3a1ca47fa2..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,57 +30,89 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.shader; -import com.jme3.math.*; -import com.jme3.texture.*; -import com.jme3.shader.BufferObject; -public enum VarType { - Float("float",float.class,Float.class), - Vector2("vec2",Vector2f.class), - Vector3("vec3",Vector3f.class), - Vector4("vec4",Vector4f.class, ColorRGBA.class), +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; - 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), +/** + * 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), - Boolean("bool",Boolean.class,boolean.class), + 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), - Matrix3(true,false,"mat3",Matrix3f.class), - Matrix4(true,false,"mat4",Matrix4f.class), + Matrix3(true, false, "mat3", Matrix3f.class), + Matrix4(true, false, "mat4", Matrix4f.class), - Matrix3Array(true,false,"mat3",Matrix3f[].class), - Matrix4Array(true,false,"mat4",Matrix4f[].class), - - TextureBuffer(false,true,"sampler1D|sampler1DShadow"), - Texture2D(false,true,"sampler2D|sampler2DShadow",Texture2D.class,Texture.class), - Texture3D(false,true,"sampler3D",Texture3D.class,Texture.class), - TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow",TextureArray.class,Texture.class), - TextureCubeMap(false,true,"samplerCube",TextureCubeMap.class,Texture.class), - Int("int",int.class,Integer.class), - BufferObject(false, false, "custom", BufferObject.class); + Matrix3Array(true, false, "mat3", Matrix3f[].class), + Matrix4Array(true, false, "mat4", Matrix4f[].class), + 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), + + UniformBufferObject(false, false, "custom", BufferObject.class), + ShaderStorageBufferObject(false, false, "custom", BufferObject.class); private boolean usesMultiData = false; private boolean textureType = false; - final private String glslType; - private Class javaTypes[]; - - VarType(String glslType,Class ...javaTypes){ + private boolean imageType = false; + private final String glslType; + private final Class[] javaTypes; + + /** + * 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]; } - } - + /** + * 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) { - usesMultiData = multiData; + this.usesMultiData = multiData; this.textureType = textureType; this.glslType = glslType; if (javaTypes != null) { @@ -89,38 +121,79 @@ public enum VarType { 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; + 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(){ + 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/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/shadow/AbstractShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java index 06e37141ff..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-2021 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(); @@ -258,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. @@ -272,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); } @@ -294,7 +290,6 @@ public RenderState getPreShadowForcedRenderState() { return shadowRenderer.getPreShadowForcedRenderState(); } - /** * returns the edge filtering mode * @@ -323,6 +318,14 @@ 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() { @@ -340,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 65cb39bc30..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,11 @@ 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; @@ -52,15 +56,16 @@ 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; import com.jme3.texture.Texture.ShadowCompareMode; import com.jme3.texture.Texture2D; -import com.jme3.texture.FrameBuffer.FrameBufferTarget; import com.jme3.ui.Picture; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; @@ -71,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 - */ + // 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() { } /** - * 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) { @@ -202,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 the desired Material (alias created) + * @param postShadowMat The desired Material instance to use (alias created). */ protected final void setPostShadowMaterial(Material postShadowMat) { this.postshadowMat = postShadowMat; @@ -218,10 +240,11 @@ 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 filtering 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) { @@ -245,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) { @@ -285,50 +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 (may be null) - * @param i the index of the desired wire color (default=White) - * @return a new Geometry + * @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; } /** @@ -342,13 +369,14 @@ 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(); @@ -363,42 +391,47 @@ public boolean isInitialized() { } /** - * Invoked once per frame to update the shadow cams according to the light - * view. + * 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 scene cam + * @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 the index of the shadow map + * @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(); @@ -415,22 +448,22 @@ public void postQueue(RenderQueue rq) { 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) { @@ -445,7 +478,7 @@ protected void renderShadowMap(int shadowMapIndex) { renderManager.getRenderer().clearBuffers(true, true, true); renderManager.setForcedRenderState(forcedRenderState); - // render shadow casters to shadow map and disables the lightfilter + // 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); @@ -453,16 +486,18 @@ protected void renderShadowMap(int shadowMapIndex) { 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 ignored + * @param r The current {@link Renderer} (ignored). */ protected void displayShadowMap(Renderer r) { Camera cam = viewPort.getCamera(); @@ -479,12 +514,19 @@ 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 @@ -526,13 +568,19 @@ public void postFrame(FrameBuffer out) { /** * 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); - private void clearMatParams(){ + /** + * 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. + */ + private void clearMatParams() { for (Material mat : matCache) { //clearing only necessary params, the others may be set by other @@ -559,14 +607,19 @@ private void clearMatParams(){ */ protected abstract void setMaterialParameters(Material material); - private void setMatParams(GeometryList l) { + /** + * 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++) { @@ -584,7 +637,7 @@ private void setMatParams(GeometryList l) { mat.setBoolean("BackfaceShadows", renderBackFacesShadows); if (fadeInfo != null) { - mat.setVector2("FadeInfo", fadeInfo); + mat.setVector2("FadeInfo", fadeInfo); } setMaterialParameters(mat); @@ -595,13 +648,19 @@ private void setMatParams(GeometryList l) { 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)) { @@ -614,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); @@ -629,43 +689,45 @@ protected void setPostShadowParams() { } /** - * 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){ + 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) { @@ -684,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) { @@ -697,39 +759,45 @@ public float getShadowZFadeLength() { } /** - * @param viewCam a Camera to define the view frustum - * @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); @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; @@ -737,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 reduce the jagged effect of 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 the desired thickness (in tenths of a pixel, default=10) + * @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)); @@ -782,45 +853,55 @@ public void setEdgesThickness(int edgesThickness) { * @return false */ @Deprecated - public boolean isFlushQueues() { return false; } + 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; @@ -864,7 +945,6 @@ public void read(JmeImporter im) throws IOException { init(assetManager, nbShadowMaps, (int) shadowMapSize); edgesThickness = ic.readFloat("edgesThickness", 1.0f); postshadowMat.setFloat("PCFEdge", edgesThickness); - } /** @@ -884,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 886abc45ec..3f41721b3c 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java @@ -64,17 +64,17 @@ public class BasicShadowRenderer implements SceneProcessor { private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); private RenderManager renderManager; private ViewPort viewPort; - final private FrameBuffer shadowFB; - final private Texture2D shadowMap; - final private Camera shadowCam; - final private Material preshadowMat; - final private Material postshadowMat; - final private Picture dispPic = new Picture("Picture"); + private final FrameBuffer shadowFB; + private final Texture2D shadowMap; + private final Camera shadowCam; + private final Material preshadowMat; + private final Material postshadowMat; + private final Picture dispPic = new Picture("Picture"); private boolean noOccluders = false; - final private Vector3f[] points = new Vector3f[8]; - final private Vector3f direction = new Vector3f(); + private final Vector3f[] points = new Vector3f[8]; + private final Vector3f direction = new Vector3f(); protected Texture2D dummyTex; - final private float shadowMapSize; + private final float shadowMapSize; protected GeometryList lightReceivers = new GeometryList(new OpaqueComparator()); protected GeometryList shadowOccluders = new GeometryList(new OpaqueComparator()); diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java index e9152594c0..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-2021 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 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 serialization. - * 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 DirectionalLight shadow filter. More info on the - * technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html - * - * @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 mean - * better quality, fewer frames per second.) + * 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,24 +104,25 @@ public float getLambda() { } /** - * 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. + * 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() { @@ -150,7 +145,6 @@ public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(shadowRenderer, "shadowRenderer", null); - } @Override @@ -159,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 0b50f8be4b..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-2021 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; @@ -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; /** @@ -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 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 184602d62d..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-2021 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 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 43de1c7f7d..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-2021 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); } } @@ -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/PssmShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java index a0d405000b..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -438,13 +438,9 @@ public void postQueue(RenderQueue rq) { renderManager.setCamera(shadowCam, false); if (debugfrustums) { -// frustumFromBound(b.casterBB,ColorRGBA.Blue ); -// frustumFromBound(b.receiverBB,ColorRGBA.Green ); -// frustumFromBound(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]); @@ -456,7 +452,11 @@ public void postQueue(RenderQueue rq) { viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true); renderManager.setLightFilter(tmpLightFilter); } - debugfrustums = false; + + if (debugfrustums) { + debugfrustums = false; + getSceneForDebug().updateGeometricState(); + } //restore setting for future rendering r.setFrameBuffer(viewPort.getOutputFrameBuffer()); @@ -465,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() { 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 9f7abee4b0..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -78,15 +78,20 @@ 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(); } /** @@ -108,10 +113,11 @@ public static void updateFrustumPoints(Camera viewCam, 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; @@ -137,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); @@ -168,13 +174,14 @@ 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(); } /** @@ -244,18 +251,21 @@ public static BoundingBox computeUnionBound(List bv) { * @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; } /** @@ -266,10 +276,10 @@ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transf * @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); @@ -282,11 +292,13 @@ 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 frustums are aligned. - return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f); + return bbox; } /** @@ -335,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(); } /** @@ -522,6 +533,11 @@ public static void updateShadowCamera(ViewPort viewPort, } 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); @@ -564,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 @@ -590,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; @@ -602,13 +621,12 @@ 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(); } /** 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 3ed677a3d1..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-2021 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 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 fewer 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 448857408b..f102f00f78 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java @@ -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; @@ -89,7 +88,7 @@ public SpotLightShadowRenderer(AssetManager assetManager, int shadowMapSize) { 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 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 f3fb5a158f..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-2022 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; @@ -58,6 +60,8 @@ 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); /** @@ -86,7 +90,6 @@ public final class AppSettings extends HashMap { @Deprecated public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3"; - /** * Use LWJGL as the display system and force using the core OpenGL3.0 renderer. *

    @@ -265,7 +268,33 @@ public final class AppSettings extends HashMap { */ 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); @@ -295,6 +324,11 @@ public final class AppSettings extends HashMap { 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); } @@ -436,7 +470,9 @@ public void load(String preferencesKey) throws BackingStoreException { 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 @@ -505,12 +541,25 @@ public void save(String preferencesKey) throws BackingStoreException { * @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; } /** @@ -522,12 +571,25 @@ public int getInteger(String key) { * @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; } /** @@ -539,12 +601,25 @@ public boolean getBoolean(String key) { * @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; } /** @@ -556,12 +631,25 @@ public String getString(String key) { * @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; } /** @@ -571,7 +659,7 @@ public float getFloat(String key) { * @param value the desired integer value */ public void putInteger(String key, int value) { - put(key, Integer.valueOf(value)); + put(key, value); } /** @@ -581,7 +669,7 @@ public void putInteger(String key, int value) { * @param value the desired boolean value */ public void putBoolean(String key, boolean value) { - put(key, Boolean.valueOf(value)); + put(key, value); } /** @@ -601,7 +689,7 @@ public void putString(String key, String value) { * @param value the desired float value */ public void putFloat(String key, float value) { - put(key, Float.valueOf(value)); + put(key, value); } /** @@ -696,9 +784,9 @@ 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
    • @@ -718,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()); } @@ -737,7 +825,7 @@ public void setAudioRenderer(String audioRenderer) { } /** - * @param value the width for the default framebuffer. + * @param value the width for the default frame buffer. * (Default: 640) */ public void setWidth(int value) { @@ -745,7 +833,7 @@ public void setWidth(int value) { } /** - * @param value the height for the default framebuffer. + * @param value the height for the default frame buffer. * (Default: 480) */ public void setHeight(int value) { @@ -753,7 +841,7 @@ public void setHeight(int value) { } /** - * Set the resolution for the default framebuffer + * 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 @@ -766,9 +854,9 @@ public void setResolution(int width, int height) { /** * Set the size of the window - * - * @param width The width in pixels (default = width of the default framebuffer) - * @param height The height in pixels (default = height of the default framebuffer) + * + * @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); @@ -802,8 +890,6 @@ public void setMinResolution(int width, int height) { setMinHeight(height); } - - /** * Set the frequency, also known as refresh rate, for the * rendering display. @@ -825,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); } @@ -844,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); } @@ -859,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); } @@ -875,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); @@ -921,7 +1007,7 @@ public void setVSync(boolean value) { * * @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); } @@ -960,7 +1046,7 @@ public void setSettingsDialogImage(String path) { /** * 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. * @@ -971,7 +1057,7 @@ public void setGammaCorrection(boolean gammaCorrection) { } /** - * Get the framerate. + * Get the frame rate. * * @return the maximum rate (in frames per second), or -1 for unlimited * @see #setFrameRate(int) @@ -1004,7 +1090,7 @@ public String getRenderer() { /** * Get the width * - * @return the width of the default framebuffer (in pixels) + * @return the width of the default frame buffer (in pixels) * @see #setWidth(int) */ public int getWidth() { @@ -1014,7 +1100,7 @@ public int getWidth() { /** * Get the height * - * @return the height of the default framebuffer (in pixels) + * @return the height of the default frame buffer (in pixels) * @see #setHeight(int) */ public int getHeight() { @@ -1180,7 +1266,7 @@ public String getAudioRenderer() { * @return true if 3-D stereo is enabled, otherwise false * @see #setStereo3D(boolean) */ - public boolean useStereo3D(){ + public boolean useStereo3D() { return getBoolean("Stereo3D"); } @@ -1215,7 +1301,7 @@ public boolean isGammaCorrection() { /** * 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. * The default value is false. @@ -1240,7 +1326,7 @@ public boolean isResizable() { /** * 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 @@ -1282,7 +1368,7 @@ public boolean isOpenCLSupport() { /** * 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 @@ -1301,7 +1387,7 @@ public String getOpenCLPlatformChooser() { * 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) */ @@ -1479,4 +1565,161 @@ public int getWindowYPosition() { 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 8d83e0960e..c2ffe912ef 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeContext.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeContext.java @@ -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, } /** @@ -102,7 +101,7 @@ public enum Type { /** * Sets the listener that will receive events relating to context * creation, update, and destroy. - * + * * @param listener the desired listener */ public void setSystemListener(SystemListener listener); @@ -225,4 +224,20 @@ public enum Type { * @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/JmeSystem.java b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java index 4a832916c8..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,7 +39,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Constructor; import java.net.URL; import java.nio.ByteBuffer; import java.util.function.BiFunction; @@ -53,7 +53,8 @@ public class JmeSystem { private static final Logger logger = Logger.getLogger(JmeSystem.class.getName()); - public static enum StorageFolderType { + + public enum StorageFolderType { Internal, External, } @@ -116,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) { @@ -130,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 { @@ -157,8 +159,6 @@ public static AssetManager newAssetManager() { return systemDelegate.newAssetManager(); } - - /** * Determine which Platform (operating system and architecture) the * application is running on. @@ -184,83 +184,83 @@ 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. * - * @deprecated Use JmeSystem.handleErrorMessage(String) instead * @param message The error message to display. May contain new line - * characters. + * characters. + * @deprecated Use JmeSystem.handleErrorMessage(String) instead */ @Deprecated - public static void showErrorDialog(String message){ + public static void showErrorDialog(String message) { handleErrorMessage(message); } - public static void handleErrorMessage(String message){ + public static void handleErrorMessage(String message) { checkDelegate(); systemDelegate.handleErrorMessage(message); } - public static void setErrorMessageHandler(Consumer handler){ + public static void setErrorMessageHandler(Consumer handler) { checkDelegate(); systemDelegate.setErrorMessageHandler(handler); } - - public static void handleSettings(AppSettings sourceSettings, boolean loadFromRegistry){ + public static void handleSettings(AppSettings sourceSettings, boolean loadFromRegistry) { checkDelegate(); systemDelegate.handleSettings(sourceSettings, loadFromRegistry); } - public static void setSettingsHandler(BiFunction handler){ + 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) { checkDelegate(); systemDelegate.initialize(settings); } - private static JmeSystemDelegate tryLoadDelegate(String className) throws InstantiationException, IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { - try { - return (JmeSystemDelegate) Class.forName(className).getDeclaredConstructor().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 | IllegalAccessException | NoSuchMethodException | IllegalArgumentException | InvocationTargetException 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 35143fca4a..c9d8b79182 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -35,6 +35,7 @@ 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; @@ -126,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() { 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 f15bc2dcff..2599949d8f 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeVersion.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeVersion.java @@ -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,7 +50,7 @@ public class JmeVersion { static { try { - props.load(JmeVersion.class.getResourceAsStream("version.properties")); + props.load(Resources.getResourceAsStream("version.properties",JmeVersion.class)); } catch (IOException | NullPointerException ex) { logger.log(Level.WARNING, "Unable to read version info!", ex); } 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 fd43d8d761..2f2518f8c3 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullContext.java +++ b/jme3-core/src/main/java/com/jme3/system/NullContext.java @@ -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(); @@ -75,26 +75,28 @@ public SystemListener getSystemListener() { } @Override - public void setSystemListener(SystemListener listener){ + public void setSystemListener(SystemListener listener) { this.listener = listener; } - protected void initInThread(){ + protected void initInThread() { logger.fine("NullContext created."); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); } - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - 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(); } @@ -102,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(); } @@ -142,7 +144,7 @@ public void sync(int fps) { } @Override - public void run(){ + public void run() { initInThread(); do { @@ -159,31 +161,27 @@ public void run(){ } @Override - public void destroy(boolean waitFor){ + public void destroy(boolean waitFor) { needClose.set(true); - if (waitFor) - waitFor(false); + if (waitFor) waitFor(false); } @Override - public void create(boolean waitFor){ - if (created.get()){ + 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); } @Override - public void restart() { - } + public void restart() {} @Override - public void setAutoFlushFrames(boolean enabled){ - } + public void setAutoFlushFrames(boolean enabled) {} @Override public MouseInput getMouseInput() { @@ -206,30 +204,28 @@ public TouchInput getTouchInput() { } @Override - public void setTitle(String title) { - } + 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) {} } } } @Override - public boolean isCreated(){ + public boolean isCreated() { return created.get(); } @@ -237,12 +233,11 @@ public boolean isCreated(){ 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. } @Override - public AppSettings getSettings(){ + public AppSettings getSettings() { return settings; } @@ -259,7 +254,7 @@ public Timer getTimer() { @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 @@ -306,4 +301,16 @@ public int getWindowXPosition() { 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 e82dea5149..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-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,14 +40,17 @@ 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; @@ -170,6 +173,11 @@ public void deleteFrameBuffer(FrameBuffer fb) { 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) { @@ -179,9 +187,6 @@ public void modifyTexture(Texture tex, Image pixels, int x, int y) { public void updateBufferData(VertexBuffer vb) { } - @Override - public void updateBufferData(BufferObject bo) { - } @Override public void deleteBuffer(VertexBuffer vb) { } @@ -293,4 +298,38 @@ public boolean isLinearizeSrgbImages() { 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 f187d2fbdf..64595a9106 100644 --- a/jme3-core/src/main/java/com/jme3/system/Platform.java +++ b/jme3-core/src/main/java/com/jme3/system/Platform.java @@ -139,7 +139,13 @@ public enum Platform { /** * Android running on unknown platform (could be x86 or mips for example). */ - Android_Other(Os.Android); + Android_Other(Os.Android), + + /** + * Generic web platform on unknown architecture + */ + Web(Os.Web, true) // assume always 64-bit, it shouldn't matter for web + ; /** @@ -165,7 +171,11 @@ public enum Os { /** * Android operating systems */ - Android + Android, + /** + * Generic web platform + */ + Web } private final boolean is64bit; 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 3b18bce1a2..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-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -80,7 +80,7 @@ public class FrameBuffer extends NativeObject { private int width = 0; private int height = 0; private int samples = 1; - final private ArrayList colorBufs = new ArrayList<>(); + private final ArrayList colorBufs = new ArrayList<>(); private RenderBuffer depthBuf = null; private int colorBufIndex = 0; private boolean srgb; @@ -179,7 +179,6 @@ public int getLayer() { return this.layer; } } - public static class FrameBufferTextureTarget extends RenderBuffer { private FrameBufferTextureTarget(){} @@ -260,12 +259,44 @@ public void addColorTarget(FrameBufferBufferTarget colorBuf){ colorBuf.slot=colorBufs.size(); colorBufs.add(colorBuf); } - + public void addColorTarget(FrameBufferTextureTarget colorBuf){ // checkSetTexture(colorBuf.getTexture(), false); // TODO: this won't work for levels. colorBuf.slot=colorBufs.size(); colorBufs.add(colorBuf); } + + /** + * Replaces the color target at the index. + *

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

      + * Color targets above the removed target will have their + * slot indices shifted accordingly. + * + * @param i + */ + public void removeColorTarget(int i) { + colorBufs.remove(i); + for (; i < colorBufs.size(); i++) { + colorBufs.get(i).slot = i; + } + } /** * Adds a texture to one of the color Buffers Array. It uses {@link TextureCubeMap} ordinal number for the @@ -363,7 +394,8 @@ protected FrameBuffer(FrameBuffer src) { * * @param format The format to use for the depth buffer. * @throws IllegalArgumentException If format is not a depth format. - * @deprecated Use setDepthTarget + * @deprecated Use + * {@link #setDepthTarget(com.jme3.texture.FrameBuffer.FrameBufferBufferTarget)} */ @Deprecated public void setDepthBuffer(Image.Format format) { @@ -656,7 +688,8 @@ 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 setDepthTarget + * @deprecated Use + * {@link #setDepthTarget(com.jme3.texture.FrameBuffer.FrameBufferTextureTarget)} */ @Deprecated public void setDepthTexture(Texture2D tex) { @@ -677,7 +710,8 @@ public void setDepthTexture(Texture2D tex) { * * @param tex the TextureArray to apply * @param layer (default=-1) - * @deprecated Use setDepthTarget + * @deprecated Use + * {@link #setDepthTarget(com.jme3.texture.FrameBuffer.FrameBufferTextureTarget)} */ @Deprecated public void setDepthTexture(TextureArray tex, int layer) { @@ -806,7 +840,7 @@ public NativeObject createDestructableClone() { @Override public long getUniqueId() { - return ((long) OBJTYPE_FRAMEBUFFER << 32) | ((long) id); + return ((long) OBJTYPE_FRAMEBUFFER << 32) | (0xffffffffL & (long) id); } /** 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 b928fd9959..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-2022 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -330,6 +330,23 @@ 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. @@ -698,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); } /** @@ -925,14 +942,19 @@ public int getMultiSamples() { * into a multisample texture (on OpenGL3.1 and higher). */ public void setMultiSamples(int multiSamples) { - if (multiSamples <= 0) + if (multiSamples <= 0) { throw new IllegalArgumentException("multiSamples must be > 0"); + } - if (getData(0) != null) - throw new IllegalArgumentException("Cannot upload data as multisample texture"); + if (multiSamples > 1) { + if (getData(0) != null) { + throw new IllegalArgumentException("Cannot upload data as multisample texture"); + } - if (hasMipmaps()) - throw new IllegalArgumentException("Multisample textures do not support mipmaps"); + if (hasMipmaps()) { + throw new IllegalArgumentException("Multisample textures do not support mipmaps"); + } + } this.multiSamples = multiSamples; } @@ -1277,7 +1299,6 @@ public void read(JmeImporter importer) throws IOException { multiSamples = capsule.readInt("multiSamples", 1); 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 00493860bb..3ba9b78128 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Texture.java +++ b/jme3-core/src/main/java/com/jme3/texture/Texture.java @@ -637,11 +637,11 @@ public void read(JmeImporter importer) throws IOException { } } - 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/TextureArray.java b/jme3-core/src/main/java/com/jme3/texture/TextureArray.java index 026489cb2a..e839399c33 100644 --- a/jme3-core/src/main/java/com/jme3/texture/TextureArray.java +++ b/jme3-core/src/main/java/com/jme3/texture/TextureArray.java @@ -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; @@ -151,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/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/ui/Picture.java b/jme3-core/src/main/java/com/jme3/ui/Picture.java index 123c244939..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-2020 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -86,6 +86,24 @@ public Picture(String name){ protected Picture(){ } + /** + * Return the height of the picture. + * + * @return the height (in pixels) + */ + public float getHeight() { + return height; + } + + /** + * Return the width of the picture. + * + * @return the width (in pixels) + */ + public float getWidth() { + return width; + } + /** * Set the width in pixels of the picture, if the width * does not match the texture's width, then the texture will @@ -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/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 b902fb6d6d..535f91d4ed 100644 --- a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java +++ b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java @@ -65,8 +65,8 @@ public final class BufferUtils { private static final BufferAllocator allocator = BufferAllocatorFactory.create(); private static boolean trackDirectMemory = false; - final private static ReferenceQueue removeCollected = new ReferenceQueue(); - final private static ConcurrentHashMap trackedBuffers = new ConcurrentHashMap(); + private static final ReferenceQueue removeCollected = new ReferenceQueue(); + private static final ConcurrentHashMap trackedBuffers = new ConcurrentHashMap(); static ClearReferences cleanupthread; /** diff --git a/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java b/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java index a582da4232..bd21d58eb4 100644 --- a/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java +++ b/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java @@ -45,11 +45,11 @@ */ public class JmeFormatter extends Formatter { - final private Date calendar = new Date(); - final private String lineSeparator; - final private MessageFormat format; - final private Object args[] = new Object[1]; - final private StringBuffer store = new StringBuffer(); + private final Date calendar = new Date(); + private final String lineSeparator; + private final MessageFormat format; + private final Object args[] = new Object[1]; + private final StringBuffer store = new StringBuffer(); public JmeFormatter(){ lineSeparator = System.getProperty("line.separator"); @@ -80,7 +80,7 @@ public String format(LogRecord record) { try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); - record.getThrown().printStackTrace(pw); + record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { diff --git a/jme3-core/src/main/java/com/jme3/util/ListMap.java b/jme3-core/src/main/java/com/jme3/util/ListMap.java index 6fd6ee55e6..0295c0c950 100644 --- a/jme3-core/src/main/java/com/jme3/util/ListMap.java +++ b/jme3-core/src/main/java/com/jme3/util/ListMap.java @@ -43,7 +43,7 @@ */ public final class ListMap extends AbstractMap implements Cloneable, Serializable { - private final static class ListMapEntry implements Map.Entry, Cloneable { + private static final class ListMapEntry implements Map.Entry, Cloneable { private final K key; private V value; diff --git a/jme3-core/src/main/java/com/jme3/util/LittleEndien.java b/jme3-core/src/main/java/com/jme3/util/LittleEndien.java index 4f42978434..5ecb31134a 100644 --- a/jme3-core/src/main/java/com/jme3/util/LittleEndien.java +++ b/jme3-core/src/main/java/com/jme3/util/LittleEndien.java @@ -62,6 +62,9 @@ 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); @@ -142,14 +145,24 @@ public double readDouble() throws IOException { @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); 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 26da19beb4..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-2021 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; - final private List bindings = new ArrayList<>(); - final 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 @@ -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); @@ -186,7 +182,7 @@ private void bind(final Binding binding) { @Override public void onAction(String name, boolean isPressed, float tpf) { if (actionName.equals(name) && isPressed) { - //reloading the material + // reloading the material binding.reload(); } } @@ -197,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 successfully reloaded"); - //System.out.println("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; @@ -243,7 +238,7 @@ public void update(float tpf) { } } } - } + } } private interface Binding { @@ -263,15 +258,14 @@ 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); } } @@ -279,7 +273,6 @@ public void reload() { @Override public String getActionName() { return geom.getName() + "Reload"; - } @Override @@ -319,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; @@ -352,7 +344,6 @@ public void reload() { } catch (IllegalArgumentException | IllegalAccessException ex) { Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex); } - } @Override 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 8d70f1d4f6..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-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,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; } } @@ -84,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; } } @@ -99,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/NativeObject.java b/jme3-core/src/main/java/com/jme3/util/NativeObject.java index c7ff725294..44ec3ad947 100644 --- a/jme3-core/src/main/java/com/jme3/util/NativeObject.java +++ b/jme3-core/src/main/java/com/jme3/util/NativeObject.java @@ -54,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. diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java b/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java index c35b91145d..fed89068f6 100644 --- a/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java +++ b/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java @@ -74,12 +74,12 @@ public class NativeObjectManager { /** * List of currently active GLObjects. */ - final private HashMap refMap = new HashMap<>(); + private final HashMap refMap = new HashMap<>(); /** * List of real objects requested by user for deletion. */ - final private ArrayDeque userDeletionQueue = new ArrayDeque<>(); + private final ArrayDeque userDeletionQueue = new ArrayDeque<>(); private static class NativeObjectRef extends PhantomReference { diff --git a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java index 3bf9381526..432f51ad18 100644 --- a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java +++ b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java @@ -83,7 +83,7 @@ public class SafeArrayList implements List, Cloneable { // the list. This was because the callers couldn't remove a child // without it being detached properly, for example. - final private Class elementType; + private final Class elementType; private List buffer; private E[] backingArray; private int size = 0; diff --git a/jme3-core/src/main/java/com/jme3/util/SkyFactory.java b/jme3-core/src/main/java/com/jme3/util/SkyFactory.java index 6219460089..48794ad991 100644 --- a/jme3-core/src/main/java/com/jme3/util/SkyFactory.java +++ b/jme3-core/src/main/java/com/jme3/util/SkyFactory.java @@ -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; @@ -194,6 +195,7 @@ 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; switch (envMapType) { 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 33c7f4ae11..f19aacd914 100644 --- a/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java @@ -59,9 +59,11 @@ 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()); @@ -860,142 +862,21 @@ private static int parity(Vector3f n1, Vector3f n) { } } + /** + * @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; + return TangentUtils.genNormalLines(mesh, scale); } - 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; - } } 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 7ae2737d26..2014eaf003 100644 --- a/jme3-core/src/main/java/com/jme3/util/TangentUtils.java +++ b/jme3-core/src/main/java/com/jme3/util/TangentUtils.java @@ -31,7 +31,16 @@ */ 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. @@ -63,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 fcc8c8071d..5cb2c30afa 100644 --- a/jme3-core/src/main/java/com/jme3/util/TempVars.java +++ b/jme3-core/src/main/java/com/jme3/util/TempVars.java @@ -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; @@ -47,7 +48,7 @@ * This returns an available instance of the TempVar class ensuring this * 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. @@ -226,4 +227,9 @@ public void release() { public final CollisionResults collisionResults = new CollisionResults(); public final float[] bihSwapTmp = new float[9]; 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 6afc55cdf3..7b85f77503 100644 --- a/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java +++ b/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java @@ -41,7 +41,7 @@ public class BlockLanguageParser { private Reader reader; - final private ArrayList statementStack = new ArrayList<>(); + private final ArrayList statementStack = new ArrayList<>(); private Statement lastStatement; private int lineNumber = 1; diff --git a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java index 5496fbb7bd..92981c2cb5 100644 --- a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java @@ -104,12 +104,12 @@ public class Cloner { /** * Keeps track of the objects that have been cloned so far. */ - final private IdentityHashMap index = new IdentityHashMap<>(); + private final IdentityHashMap index = new IdentityHashMap<>(); /** * Custom functions for cloning objects. */ - final private Map functions = new HashMap<>(); + private final Map functions = new HashMap<>(); /** * Cache the clone methods once for all cloners. diff --git a/jme3-core/src/main/java/com/jme3/util/functional/Function.java b/jme3-core/src/main/java/com/jme3/util/functional/Function.java index 4766ed9034..c4948072e3 100644 --- a/jme3-core/src/main/java/com/jme3/util/functional/Function.java +++ b/jme3-core/src/main/java/com/jme3/util/functional/Function.java @@ -1,6 +1,36 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.util.functional; - public interface Function { R eval(T t); } \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java b/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java index 0f8089d530..63dec2f682 100644 --- a/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java +++ b/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java @@ -1,4 +1,34 @@ - +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.util.functional; public interface NoArgFunction { diff --git a/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java b/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java index 014c6787d5..42dd416908 100644 --- a/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java +++ b/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java @@ -1,4 +1,34 @@ - +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.util.functional; public interface NoArgVoidFunction { diff --git a/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java b/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java index e7726b2c11..ccafb2e5c8 100644 --- a/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java +++ b/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java @@ -1,4 +1,34 @@ - +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.util.functional; public interface VoidFunction { diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java index 27375469e4..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 @@ -34,6 +34,7 @@ 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; @@ -43,29 +44,25 @@ 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 3-D software. - * 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()); @@ -116,36 +113,57 @@ public static void generate(Spatial s){ } else if (s instanceof Geometry) { Geometry g = (Geometry) s; Mesh mesh = g.getMesh(); - - Mesh.Mode mode = mesh.getMode(); - boolean hasTriangles; - switch (mode) { - case Points: - case Lines: - case LineStrip: - case LineLoop: - hasTriangles = false; // skip this mesh - break; - - case Triangles: - case TriangleFan: - case TriangleStrip: - hasTriangles = true; - break; - - default: - String message = "Tangent generation isn't implemented for mode=" + mode; - throw new UnsupportedOperationException(message); + boolean success = generateTangents(mesh); + if (!success) { + logger.log(Level.SEVERE, "Failed to generate tangents for geometry {0}", g.getName()); } + } + } - if (hasTriangles) { - MikkTSpaceImpl context = new MikkTSpaceImpl(mesh); - if (!genTangSpaceDefault(context)) { - logger.log(Level.SEVERE, "Failed to generate tangents for geometry {0}", g.getName()); - } - TangentUtils.generateBindPoseTangentsIfNecessary(mesh); - } + 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) { @@ -279,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; } } 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-vr/src/main/java/com/jme3/input/vr/openvr/package-info.java b/jme3-core/src/main/java/com/jme3/util/struct/Struct.java similarity index 79% rename from jme3-vr/src/main/java/com/jme3/input/vr/openvr/package-info.java rename to jme3-core/src/main/java/com/jme3/util/struct/Struct.java index e74abe7d49..8b49d00248 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/package-info.java +++ b/jme3-core/src/main/java/com/jme3/util/struct/Struct.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,15 @@ * NEGLIGENCE OR 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; + /** - * user-input classes for devices that use the OpenVR API + * 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 */ -package com.jme3.input.vr.openvr; +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-vr/src/main/java/com/jme3/input/vr/osvr/package-info.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructField.java similarity index 81% rename from jme3-vr/src/main/java/com/jme3/input/vr/osvr/package-info.java rename to jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructField.java index fb1c9ca58d..f9025985fd 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/package-info.java +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructField.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,8 +29,16 @@ * 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 for devices that use the Open Source Virtual Reality - * (OSVR) API - */ -package com.jme3.input.vr.osvr; +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/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.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 f0b7ee5a6f..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,6 +2,7 @@ MaterialDef Phong Lighting Deferred { MaterialParameters { + Int BoundDrawBuffer // Use more efficient algorithms to improve performance Boolean LowQuality @@ -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/Lighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag index c3066aea15..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 compliant with normal maps generated with blender. - //see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898 - //for more explanation. - vec3 normal = normalize((normalHeight.xyz * vec3(2.0,-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 d4242ee8ad..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, (TransformWorld(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 8357527009..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,343 +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; +// 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 -uniform float m_Roughness; -uniform float m_Metallic; +#import "Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib" -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; +#ifdef DEBUG_VALUES_MODE + uniform int m_DebugValuesMode; #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 - -#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; - varying vec4 wTangent; -#endif -#ifdef NORMALSCALE - uniform float m_NormalScale; -#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 - - //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; - float Roughness = aoRoughnessMetallicValue.g * max(m_Roughness, 1e-4); - float Metallic = aoRoughnessMetallicValue.b * 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 compliant with normal maps generated with blender. - //see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898 - //for more explanation. - #ifdef NORMALSCALE - vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)) * vec3(m_NormalScale, m_NormalScale, 1.0)); - #else - vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0))); - #endif - normal = normalize(tbnMat * normal); - //normal = normalize(normal * inverse(tbnMat)); - #else - 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 - - #if defined(AO_PACKED_IN_MR_MAP) && defined(USE_PACKED_MR) - ao = aoRoughnessMetallicValue.rrr; - #endif - - #ifdef AO_STRENGTH - ao = 1.0 + m_AoStrength * (ao - 1.0); - // sanity check - ao = clamp(ao, 0.0, 1.0); - #endif - - float ndotv = max( dot( normal, viewDir ),0.0); - for( int i = 0;i < NB_LIGHTS; i+=3){ - vec4 lightColor = g_LightData[i]; - 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 + // Calculate env probes + PBRLightingUtils_computeProbesContribution(surface); - 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 - 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); - #ifdef EMISSIVE - emissive *= m_Emissive; - #endif - #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 57954738a7..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,6 +1,7 @@ MaterialDef PBR Lighting { MaterialParameters { + Int BoundDrawBuffer // Alpha threshold for fragment discarding Float AlphaDiscardThreshold (AlphaTestFallOff) @@ -55,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 @@ -124,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 @@ -141,7 +173,8 @@ MaterialDef PBR Lighting { ViewMatrix } - Defines { + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer BASECOLORMAP : BaseColorMap NORMALMAP : NormalMap NORMALSCALE : NormalScale @@ -161,6 +194,9 @@ 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 @@ -170,14 +206,23 @@ MaterialDef PBR Lighting { 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/PreShadowPBR.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadowPBR.frag WorldParameters { WorldViewProjectionMatrix @@ -187,6 +232,7 @@ MaterialDef PBR Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones INSTANCING : UseInstancing @@ -206,8 +252,8 @@ MaterialDef PBR Lighting { Technique PostShadow { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadowPBR.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadowPBR.frag WorldParameters { WorldViewProjectionMatrix @@ -217,6 +263,7 @@ MaterialDef PBR Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge @@ -241,8 +288,8 @@ MaterialDef PBR 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 @@ -253,6 +300,7 @@ MaterialDef PBR Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer BASECOLORMAP_ALPHA : BaseColorMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing @@ -264,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 @@ -274,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 2e7cd4347e..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 compliant with normal maps generated with blender. - //see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898 - //for more explanation. - vec3 normal = normalize((normalHeight.xyz * vec3(2.0,-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/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 8972d7d9cf..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,6 +17,7 @@ MaterialDef Debug Normals { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer INSTANCING : UseInstancing } } 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 b4710db62e..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 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,6 +17,7 @@ MaterialDef Sky Plane { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer SPHERE_MAP : SphereMap EQUIRECT_MAP : EquirectMap } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/SkyNonCube.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/SkyNonCube.j3md index 15bb8c8be9..d039af39f5 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/SkyNonCube.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/SkyNonCube.j3md @@ -1,13 +1,14 @@ MaterialDef Sky Plane { MaterialParameters { + Int BoundDrawBuffer Texture2D Texture Boolean SphereMap 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,6 +17,7 @@ MaterialDef Sky Plane { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer SPHERE_MAP : SphereMap EQUIRECT_MAP : EquirectMap } 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 4617b4b354..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) @@ -65,8 +66,8 @@ MaterialDef Unshaded { } 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 @@ -75,6 +76,7 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer INSTANCING : UseInstancing SEPARATE_TEXCOORD : SeparateTexCoord HAS_COLORMAP : ColorMap @@ -92,8 +94,8 @@ MaterialDef Unshaded { 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 @@ -104,6 +106,7 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer COLORMAP_ALPHA : ColorMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing @@ -114,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 @@ -125,6 +128,7 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer COLOR_MAP : ColorMap DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones @@ -145,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 @@ -156,6 +160,7 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge @@ -181,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 @@ -191,6 +196,7 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer NEED_TEXCOORD1 HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor 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/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 c3d0c51afc..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 @@ -35,8 +36,8 @@ MaterialDef Post Shadow { } 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/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/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/ShaderLib/GLSLCompat.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib index 1805595627..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 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/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/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 f16d42cfb4..f6254da82e 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib @@ -6,6 +6,22 @@ #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){ float fresnelFact = pow(2.0, (-5.55473*vh - 6.98316) * vh); @@ -34,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); @@ -53,8 +68,6 @@ 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 normal Distribution function float alpha2 = alpha * alpha; float sum = ((ndoth * ndoth) * (alpha2 - 1.0) + 1.0); @@ -89,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 ); diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib index 34eba74237..2910679a47 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib @@ -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/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 89fb8e67f8..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,7 +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.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/plugins/java/com/jme3/asset/plugins/ClasspathLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ClasspathLocator.java index dbdd33c65a..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 @@ -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; @@ -87,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/UrlAssetInfo.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java index 6dad1ea566..b634e2abf1 100644 --- a/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java @@ -47,7 +47,7 @@ */ public class UrlAssetInfo extends AssetInfo { - final private URL url; + private final URL url; private InputStream in; public static UrlAssetInfo create(AssetManager assetManager, AssetKey key, URL url) throws IOException { diff --git a/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java b/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java index bdeb015b60..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-2021 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,39 +48,62 @@ 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 { - final 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 @@ -89,128 +113,176 @@ public void setTime(float time) { } 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 = 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; } } @@ -218,18 +290,28 @@ private AudioData load(AssetInfo info, InputStream inputStream, boolean stream) @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/export/binary/BinaryExporter.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java index 3bd039714a..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,7 +38,13 @@ 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; @@ -116,39 +122,40 @@ * * @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; - final private 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 - final private HashMap classes - = new HashMap<>(); - - final 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. @@ -163,9 +170,11 @@ public static T saveAndLoad(AssetManager assetManager, T obj 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); @@ -191,28 +200,25 @@ public void save(Savable object, OutputStream os) throws IOException { // 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 + 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); + 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)); // 5. "full class-name size" os.write(classBytes); // 6. "full class name" @@ -236,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); @@ -286,17 +290,14 @@ public void save(Savable object, OutputStream os) throws IOException { // 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}); } } @@ -305,14 +306,15 @@ private String getChunk(BinaryIdContentPair pair) { .getContent().bytes.length)); } - private 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; } @@ -328,9 +330,9 @@ protected byte[] fixClassAlias(byte[] bytes, int width) { } @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()) { + if (parentDirectory != null && !parentDirectory.exists() && createDirectories) { parentDirectory.mkdirs(); } @@ -345,7 +347,7 @@ 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<>(); @@ -361,10 +363,10 @@ public int processBinarySavable(Savable object) throws IOException { 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? @@ -379,7 +381,6 @@ public int processBinarySavable(Savable object) throws IOException { object.write(this); newPair.getContent().finish(); return newPair.getId(); - } protected byte[] generateTag() { @@ -401,4 +402,4 @@ private BinaryIdContentPair generateIdContentPair(BinaryClassObject bco) { 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 42a2be80e7..03c54823de 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java @@ -54,16 +54,16 @@ public final class BinaryImporter implements JmeImporter { private AssetManager assetManager; //Key - alias, object - bco - final private HashMap classes + private final HashMap classes = new HashMap<>(); //Key - id, object - the savable - final private HashMap contentTable + private final HashMap contentTable = new HashMap<>(); //Key - savable, object - capsule - final private IdentityHashMap capsuleTable + private final IdentityHashMap capsuleTable = new IdentityHashMap<>(); //Key - id, object - location in the file - final private HashMap locationTable + private final HashMap locationTable = new HashMap<>(); public static boolean debug = false; @@ -329,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/ByteUtils.java b/jme3-core/src/plugins/java/com/jme3/export/binary/ByteUtils.java index 531a42af6a..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 @@ -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; @@ -50,6 +52,77 @@ public class ByteUtils { 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 * @@ -125,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); @@ -187,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); @@ -263,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); @@ -326,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); @@ -382,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); @@ -440,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); } @@ -470,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/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index 0a0c0403c2..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-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,8 +51,6 @@ import com.jme3.util.blockparser.Statement; import com.jme3.util.clone.Cloner; import jme3tools.shader.Preprocessor; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; @@ -64,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; @@ -76,10 +73,10 @@ public class J3MLoader implements AssetLoader { private Material material; private TechniqueDef technique; private RenderState renderState; - final private ArrayList presetDefines = new ArrayList<>(); + private final ArrayList presetDefines = new ArrayList<>(); - final private List> shaderLanguages; - final private EnumMap shaderNames; + private final List> shaderLanguages; + private final EnumMap shaderNames; private static final String whitespacePattern = "\\p{javaWhitespace}+"; @@ -88,7 +85,6 @@ public J3MLoader() { shaderNames = new EnumMap<>(Shader.ShaderType.class); } - // : private void readShaderStatement(String statement) throws IOException { String[] split = statement.split(":"); @@ -105,7 +101,6 @@ private void readShaderStatement(String statement) throws IOException { } } - private void readShaderDefinition(Shader.ShaderType shaderType, String name, String... languages) { shaderNames.put(shaderType, name); @@ -131,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); } @@ -299,7 +294,7 @@ private Texture parseTextureType(final VarType type, final String value) { for (final TextureOptionValue textureOptionValue : textureOptionValues) { textureOptionValue.applyToTexture(texture); } - } + } return texture; } @@ -313,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); @@ -538,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{ @@ -555,7 +550,6 @@ private void readDefines(List defineList) throws IOException{ for (Statement statement : defineList){ readDefine(statement.getLine()); } - } private void readTechniqueStatement(Statement statement) throws IOException{ @@ -602,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) { @@ -667,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. @@ -724,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; @@ -755,7 +762,7 @@ 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); @@ -767,24 +774,26 @@ private void loadFromRoot(List roots) throws IOException{ 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); } } } @@ -799,6 +808,7 @@ 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"); } 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 f23397b635..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); @@ -80,6 +79,5 @@ public Object load(AssetInfo assetInfo) throws IOException { } return loaderDelegate.readNodesDefinitions(roots.get(0).getContents(), key); - } } 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 2a3a277f8a..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-2020 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -184,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; } diff --git a/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java b/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java index 2e229c2d8e..63b8ba68c7 100644 --- a/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java @@ -43,7 +43,7 @@ public class GLSLLoader implements AssetLoader { private AssetManager assetManager; - final private Map dependCache = new HashMap<>(); + private final Map dependCache = new HashMap<>(); /** * Used to load {@link ShaderDependencyNode}s. diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java index f7881e45c2..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 @@ -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; @@ -86,6 +87,10 @@ public class DDSLoader implements AssetLoader { private static final int PF_DX10 = 0x30315844; // a DX10 format 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); @@ -156,7 +161,7 @@ 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 { @@ -223,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) { @@ -288,7 +293,7 @@ private void readPixelFormat() throws IOException { compressed = true; int fourcc = in.readInt(); int swizzle = in.readInt(); - in.skipBytes(16); + ByteUtils.skipFully(in, 16); switch (fourcc) { case PF_DXT1: @@ -341,6 +346,22 @@ private void readPixelFormat() throws IOException { 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)); } 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 a07c3d215e..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 @@ -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); @@ -202,7 +204,11 @@ 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(w / 4f); int blocksY = (int) FastMath.ceil(h / 4f); 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 183049f9ec..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 @@ -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; @@ -180,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{ @@ -200,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); 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 859d06fc77..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 @@ -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; @@ -115,12 +116,7 @@ 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 (needEndianFlip){ flipScanline(scanline); 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 13bf130977..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 @@ -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; @@ -158,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; @@ -168,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. 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 9cd78b35a3..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 @@ -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; @@ -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)); } @@ -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 @@ -305,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/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/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/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 index 7f75ec6550..4c0e760bc2 100644 --- a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java +++ b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java @@ -41,6 +41,57 @@ * @author Stephen Gold */ public class TestBoundingSphere { + /** + * Verify that equals() behaves as expected. + */ + @Test + public void testEquals() { + BoundingSphere bs1 = new BoundingSphere(0f, new Vector3f(3f, 4f, 5f)); + BoundingSphere bs2 = new BoundingSphere(-0f, new Vector3f(3f, 4f, 5f)); + + BoundingSphere bs3 = new BoundingSphere(1f, new Vector3f(3f, 0f, 2f)); + BoundingSphere bs4 = new BoundingSphere(1f, new Vector3f(3f, -0f, 2f)); + + BoundingSphere bs5 = new BoundingSphere(2f, new Vector3f(4f, 5f, 6f)); + BoundingSphere bs6 = (BoundingSphere) bs5.clone(); + bs6.setCheckPlane(1); + + // Clones are equal to their base instances: + Assert.assertEquals(bs1, bs1.clone()); + Assert.assertEquals(bs2, bs2.clone()); + Assert.assertEquals(bs3, bs3.clone()); + Assert.assertEquals(bs4, bs4.clone()); + Assert.assertEquals(bs5, bs5.clone()); + Assert.assertEquals(bs6, bs6.clone()); + + Assert.assertNotEquals(bs1, bs2); // because their radii differ + Assert.assertNotEquals(bs3, bs4); // because their centers differ + Assert.assertEquals(bs5, bs6); // because check planes are ignored + } + + /** + * Verify that isSimilar() behaves as expected. + */ + @Test + public void testIsSimilar() { + BoundingSphere bs1 = new BoundingSphere(0f, new Vector3f(3f, 4f, 5f)); + BoundingSphere bs2 = new BoundingSphere(0.1f, new Vector3f(3f, 4f, 5f)); + + BoundingSphere bs3 = new BoundingSphere(1f, new Vector3f(3f, 4f, 2f)); + BoundingSphere bs4 = new BoundingSphere(1f, new Vector3f(3f, 3.9f, 2f)); + + BoundingSphere bs5 = new BoundingSphere(2f, new Vector3f(4f, 5f, 6f)); + BoundingSphere bs6 = (BoundingSphere) bs5.clone(); + bs6.setCheckPlane(1); + + Assert.assertFalse(bs1.isSimilar(bs2, 0.09999f)); + Assert.assertTrue(bs1.isSimilar(bs2, 0.10001f)); + + Assert.assertFalse(bs3.isSimilar(bs4, 0.09999f)); + Assert.assertTrue(bs3.isSimilar(bs4, 0.10001f)); + + Assert.assertTrue(bs5.isSimilar(bs6, 0f)); // check planes are ignored + } /** * Verify that an infinite bounding sphere can be merged with a very 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/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 7fd574a123..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 @@ -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 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 e48b33b2dd..6edd3ed34a 100644 --- a/jme3-core/src/test/java/com/jme3/material/MaterialTest.java +++ b/jme3-core/src/test/java/com/jme3/material/MaterialTest.java @@ -104,7 +104,7 @@ public void testSelectDefaultTechnique_GLSL120Cap_MultipleLangs() { material.selectTechnique("Default", renderManager); - checkRequiredCaps(Caps.GLSL100, Caps.GLSL120); + checkRequiredCaps(Caps.GLSL120); } @Test 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/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/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/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 c37df8389f..929281819e 100644 --- a/jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java +++ b/jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java @@ -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/SpatialTest.java b/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java index 038bdc3fc8..48c9147920 100644 --- a/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java +++ b/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java @@ -31,6 +31,9 @@ */ 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; @@ -119,4 +122,33 @@ public void testAddControlAt() { 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/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/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 e2bc6b5cc5..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-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -91,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(""); } @@ -100,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(""); } @@ -120,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(""); } @@ -148,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); @@ -191,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 index 91ab893ad5..5ba4ecd2c7 100644 --- a/jme3-core/src/test/java/com/jme3/shader/GLSLPreprocessorTest.java +++ b/jme3-core/src/test/java/com/jme3/shader/GLSLPreprocessorTest.java @@ -75,4 +75,52 @@ public void testFOR() throws Exception{ 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/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 ca981877eb..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; @@ -45,7 +47,7 @@ public void writeImageFile(OutputStream outStream, String format, ByteBuffer ima @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 ba6b833252..b23d1846ee 100644 --- a/jme3-core/src/test/java/com/jme3/system/TestUtil.java +++ b/jme3-core/src/test/java/com/jme3/system/TestUtil.java @@ -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; @@ -55,7 +57,13 @@ public static AssetManager createAssetManager() { 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/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/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/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 dc909e9cc8..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 @@ -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 diff --git a/jme3-core/src/test/resources/bad-booleans2.j3md b/jme3-core/src/test/resources/bad-booleans2.j3md index c36398f4cc..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 @@ -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 diff --git a/jme3-core/src/test/resources/bad-booleans3.j3md b/jme3-core/src/test/resources/bad-booleans3.j3md index 0410e03202..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 @@ -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 diff --git a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java index ca71946b8d..648f85f070 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java @@ -108,7 +108,7 @@ public class LodGenerator { private List triangleList; private List vertexList = new ArrayList<>(); private float meshBoundingSphereRadius; - final private Mesh mesh; + private final Mesh mesh; /** * Enumerate criteria for removing triangles. @@ -235,7 +235,7 @@ public String toString() { /** * Comparator used to sort vertices according to their collapse cost */ - final private Comparator collapseComparator = new Comparator() { + private final Comparator collapseComparator = new Comparator() { @Override public int compare(Vertex o1, Vertex o2) { if (Float.compare(o1.collapseCost, o2.collapseCost) == 0) { @@ -576,13 +576,22 @@ 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; } /** diff --git a/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java b/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java index feec784320..9447b2d956 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java @@ -121,11 +121,11 @@ public class TextureAtlas { private static final Logger logger = Logger.getLogger(TextureAtlas.class.getName()); private Map images; - final private int atlasWidth, atlasHeight; - final private Format format = Format.ABGR8; - final private Node root; - final private Map locationMap; - final private Map mapNameMap; + private final int atlasWidth, atlasHeight; + private final Format format = Format.ABGR8; + private final Node root; + private final Map locationMap; + private final Map mapNameMap; private String rootMapName; public TextureAtlas(int width, int height) { diff --git a/jme3-core/src/tools/java/jme3tools/shader/Preprocessor.java b/jme3-core/src/tools/java/jme3tools/shader/Preprocessor.java index e99c0a7ad4..7c429e1e7b 100644 --- a/jme3-core/src/tools/java/jme3tools/shader/Preprocessor.java +++ b/jme3-core/src/tools/java/jme3tools/shader/Preprocessor.java @@ -58,6 +58,7 @@ public static InputStream apply(InputStream in) throws IOException { String code = bos.toString("UTF-8"); code = Preprocessor.forMacro(code); + code = Preprocessor.structMacro(code); return new ByteArrayInputStream(code.getBytes("UTF-8")); } @@ -118,4 +119,96 @@ public static String forMacro(String 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-desktop/src/main/java/com/jme3/app/AppletHarness.java b/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java index 2ce2a55885..eddf09b333 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java +++ b/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java @@ -34,6 +34,8 @@ 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; @@ -150,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(); 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 f5411bf479..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 @@ -33,6 +33,7 @@ import com.jme3.asset.AssetInfo; 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; @@ -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) { @@ -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(); 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 2867921ef0..b4c5059a07 100644 --- a/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java @@ -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 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 6c84aaae22..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 @@ -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,10 +54,10 @@ 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(){ } 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 4e4bc2248b..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-2021 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(); @@ -144,28 +153,21 @@ public void setInputListener(RawInputListener listener) { 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() { - @Override - public void run() { - component.setCursor(newVisible ? null : getTransparentCursor()); - if (!newVisible) { + SwingUtilities.invokeLater(() -> { + component.setCursor(newVisible ? null : getTransparentCursor()); + if (!newVisible) { recenterMouse(component); - } } }); -// } } } @@ -314,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); 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 1232f04809..d1911e3482 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java @@ -31,7 +31,6 @@ */ package com.jme3.system; - import com.jme3.input.AWTKeyInput; import com.jme3.input.AWTMouseInput; import com.jme3.input.JoyInput; @@ -49,104 +48,104 @@ */ 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); - } + /** + * 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 @@ -154,87 +153,85 @@ 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) { + @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 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 destroy(final boolean waitFor) { + if (backgroundContext == null) throw new IllegalStateException("Not created"); + // destroy wrapped context + backgroundContext.destroy(waitFor); + } /** * Returns the height of the framebuffer. @@ -275,4 +272,16 @@ public int getWindowXPosition() { 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/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java index 645a83b9ed..81c197a3c5 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -39,6 +39,8 @@ import com.jme3.system.JmeContext.Type; 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; @@ -70,7 +72,7 @@ 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) { 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 209034c1e8..c0eb8613e6 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java @@ -44,6 +44,8 @@ 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. *
      @@ -273,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); @@ -293,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 { @@ -370,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) { @@ -400,7 +403,7 @@ public static void extractNativeLibrary(Platform platform, String name, File tar return; } - URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar); + URL url = Resources.getResource(pathInJar); if (url == null) { return; } @@ -461,7 +464,7 @@ public static void loadNativeLibrary(String name, boolean isRequired) { return; } - URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar); + URL url = Resources.getResource(pathInJar); if (url == null) { if (isRequired) { 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 3125e1f751..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 @@ -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,19 +67,19 @@ 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(); public AwtPanel(PaintMode paintMode) { @@ -180,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(); } @@ -190,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; } @@ -210,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); } @@ -242,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) { 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 032c8457eb..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 @@ -109,9 +109,8 @@ public void destroy() { } } - 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); @@ -187,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."); } } @@ -230,18 +228,17 @@ 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(); } @@ -249,14 +246,14 @@ private void destroyInThread(){ 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"); } @@ -267,8 +264,7 @@ public void create(boolean 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); @@ -328,4 +324,16 @@ public int getWindowXPosition() { 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-effects/src/main/java/com/jme3/post/filters/CrossHatchFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/CrossHatchFilter.java index 87d205e0fd..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-2021 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 @@ -329,4 +334,58 @@ public float getLuminance4() { 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 06f6f56dc3..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-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -227,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 @@ -243,4 +243,4 @@ public void read(JmeImporter im) throws IOException { focusRange = ic.readFloat("focusRange", 10f); debugUnfocus = ic.readBoolean("debugUnfocus", false); } -} +} \ No newline at end of file diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/FXAAFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/FXAAFilter.java index d9a29762ef..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-2021 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; /** * demo @@ -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/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/PosterizationFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/PosterizationFilter.java index a13fabf633..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-2021 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 @@ -158,4 +163,38 @@ public float getGamma() { 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/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/ssao/SSAOFilter.java b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java index 4223aa38f0..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-2021 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; @@ -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 * (screenWidth / (float) screenHeight); - frustumCorner = new Vector3f(farX, farY, vp.getCamera().getFrustumFar()); + 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,18 +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 + * 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 width (default=0.1) + * @param bias The desired bias value (default: 0.1f). */ public void setBias(float bias) { this.bias = bias; @@ -219,62 +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.5 + * Sets the ambient occlusion intensity. A higher intensity makes the ambient + * occlusion effect more pronounced. * - * @param intensity the desired intensity (default=1.5) + * @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 + * 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.1) + * @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 + * 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.2) + * @param scale The desired distance (default: 0.2f). */ public void setScale(float scale) { this.scale = scale; @@ -284,7 +281,30 @@ public void setScale(float scale) { } /** - * debugging only , will be removed + * 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() { @@ -292,7 +312,7 @@ public boolean isUseAo() { } /** - * debugging only , will be removed + * debugging only, will be removed * * @param useAo true to enable, false to disable (default=true) */ @@ -301,22 +321,10 @@ public void setUseAo(boolean 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() { @@ -324,7 +332,7 @@ public boolean isUseOnlyAo() { } /** - * debugging only , will be removed + * debugging only, will be removed * * @param useOnlyAo true to enable, false to disable (default=false) */ @@ -343,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 @@ -353,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/water/WaterFilter.java b/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java index 509cbd0ed0..0d84b23674 100644 --- a/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java +++ b/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java @@ -1045,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); } } 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 index f3e499d64c..363d078a1b 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.frag @@ -37,9 +37,11 @@ * 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. * - * Supports GLSL100 GLSL110 GLSL120 GLSL130. */ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" + //constant inputs from java source uniform float m_redChannelExponent; uniform float m_greenChannelExponent; @@ -55,7 +57,7 @@ uniform float m_lowerLimit; uniform float m_upperLimit; //container for the input from post.vert -uniform sampler2D m_Texture; +uniform COLORTEXTURE m_Texture; //varying input from post.vert vertex shader varying vec2 texCoord; diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.j3md index 83c0e1125f..7c2ee7bea7 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.j3md @@ -4,6 +4,7 @@ MaterialDef ColorAdjustmentFilter { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Texture2D Texture Float redChannelExponent @@ -15,14 +16,17 @@ MaterialDef ColorAdjustmentFilter { Float greenChannelScale Float blueChannelScale } + Technique { - VertexShader GLSL150 GLSL300 GLSL310 GLSL320: Common/MatDefs/Post/Post15.vert - FragmentShader GLSL150 GLSL300 GLSL310 GLSL320: Common/MatDefs/Post/ContrastAdjustment15.frag - } + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/ContrastAdjustment.frag + WorldParameters { + } - Technique { - VertexShader GLSL100 GLSL110 GLSL120 GLSL130: Common/MatDefs/Post/Post.vert - FragmentShader GLSL100 GLSL110 GLSL120 GLSL130: Common/MatDefs/Post/ContrastAdjustment.frag + 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/ContrastAdjustment15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment15.frag deleted file mode 100644 index c92bbc9901..0000000000 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment15.frag +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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. - * - * Supports glsl150 and glsl150+ including android GLES glsl300, glsl310 and glsl320. - */ - -#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 post15.vert -uniform COLORTEXTURE m_Texture; - -//varying input from post15.vert vertex shader -in vec2 texCoord; - -//the output color -out vec4 fragColor; - -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. - fragColor = color; -} \ 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 eb03e8f565..edd29d88c5 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag @@ -86,4 +86,4 @@ void main() { vec4 texVal = texture2D(m_Texture, texCoord); gl_FragColor = vec4(FxaaPixelShader(posPos, m_Texture, g_ResolutionInverse), texVal.a); -} \ No newline at end of file +} diff --git a/jme3-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/ssao.j3md b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/ssao.j3md index 0f5b5433e7..f61dd1a32e 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/ssao.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/ssao.j3md @@ -1,6 +1,7 @@ MaterialDef SSAO { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture @@ -18,7 +19,7 @@ MaterialDef SSAO { } 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/SSAO/ssao.frag WorldParameters { @@ -28,6 +29,7 @@ MaterialDef SSAO { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples RESOLVE_DEPTH_MS : NumSamplesDepth APPROXIMATE_NORMALS : ApproximateNormals diff --git a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/ssaoBlur.j3md b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/ssaoBlur.j3md index 2314876d6d..773d34c363 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/ssaoBlur.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/ssaoBlur.j3md @@ -1,6 +1,7 @@ MaterialDef SSAOBlur { - MaterialParameters { + MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture @@ -22,6 +23,7 @@ MaterialDef SSAOBlur { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer USE_AO : UseAo USE_ONLY_AO : UseOnlyAo RESOLVE_MS : NumSamples diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/SimpleWater.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Water/SimpleWater.j3md index 7ef8775b3a..710dcd5151 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Water/SimpleWater.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Water/SimpleWater.j3md @@ -1,6 +1,7 @@ MaterialDef Simple Water { MaterialParameters { + Int BoundDrawBuffer Texture2D water_reflection Texture2D water_refraction Texture2D water_depthmap @@ -18,8 +19,8 @@ MaterialDef Simple Water { } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Water/simple_water.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Water/simple_water.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Water/simple_water.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Water/simple_water.frag WorldParameters { WorldViewProjectionMatrix @@ -27,5 +28,9 @@ MaterialDef Simple Water { Resolution CameraPosition } + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } + } } \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md index 49c5a06e53..f75e263aba 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md @@ -1,6 +1,7 @@ MaterialDef Advanced Water { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D FoamMap @@ -59,7 +60,8 @@ MaterialDef Advanced Water { } Defines { - RESOLVE_MS : NumSamples + BOUND_DRAW_BUFFER: BoundDrawBuffer + RESOLVE_MS : NumSamples RESOLVE_DEPTH_MS : NumSamplesDepth ENABLE_RIPPLES : UseRipples ENABLE_HQ_SHORELINE : UseHQShoreline diff --git a/jme3-effects/src/test/java/com/jme3/post/filters/CrossHatchFilterTest.java b/jme3-effects/src/test/java/com/jme3/post/filters/CrossHatchFilterTest.java new file mode 100644 index 0000000000..5b2006e55e --- /dev/null +++ b/jme3-effects/src/test/java/com/jme3/post/filters/CrossHatchFilterTest.java @@ -0,0 +1,112 @@ +/* + * 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.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.math.ColorRGBA; +import org.junit.Assert; +import org.junit.Test; + +/** + * Automated tests for the {@code CrossHatchFilter} class. + * + * @author sgold + */ +public class CrossHatchFilterTest { + /** + * Tests serialization and de-serialization of a {@code CrossHatchFilter}. + * This test would've detected JME issue #2169, for instance. + */ + @Test + public void testSaveAndLoad() { + CrossHatchFilter filter = new CrossHatchFilter(); + + // Verify the default parameter values: + verifyDefaults(filter); + + // Set parameters to new values: + filter.setColorInfluenceLine(0.7f); + filter.setColorInfluencePaper(0.2f); + filter.setEnabled(false); + filter.setFillValue(0.95f); + filter.setLineColor(ColorRGBA.Blue.clone()); + filter.setLineDistance(3f); + filter.setLineThickness(0.9f); + filter.setLuminanceLevels(0.95f, 0.75f, 0.54f, 0.32f, 0.21f); + filter.setPaperColor(ColorRGBA.Yellow.clone()); + + // Create a duplicate filter using serialization: + AssetManager assetManager = new DesktopAssetManager(); + CrossHatchFilter copy = BinaryExporter.saveAndLoad(assetManager, filter); + + // Verify the parameter values of the copy: + Assert.assertEquals(0.7f, copy.getColorInfluenceLine(), 0f); + Assert.assertEquals(0.2f, copy.getColorInfluencePaper(), 0f); + Assert.assertEquals(0.95f, copy.getFillValue(), 0f); + Assert.assertEquals(ColorRGBA.Blue, copy.getLineColor()); + Assert.assertEquals(3f, copy.getLineDistance(), 0f); + Assert.assertEquals(0.9f, copy.getLineThickness(), 0f); + Assert.assertEquals(0.95f, copy.getLuminance1(), 0f); + Assert.assertEquals(0.75f, copy.getLuminance2(), 0f); + Assert.assertEquals(0.54f, copy.getLuminance3(), 0f); + Assert.assertEquals(0.32f, copy.getLuminance4(), 0f); + Assert.assertEquals(0.21f, copy.getLuminance5(), 0f); + Assert.assertEquals("CrossHatchFilter", copy.getName()); + Assert.assertEquals(ColorRGBA.Yellow, copy.getPaperColor()); + Assert.assertFalse(copy.isEnabled()); + } + + /** + * Verify some default values of a newly instantiated + * {@code CrossHatchFilter}. + * + * @param filter (not null, unaffected) + */ + private void verifyDefaults(CrossHatchFilter filter) { + Assert.assertEquals(0.8f, filter.getColorInfluenceLine(), 0f); + Assert.assertEquals(0.1f, filter.getColorInfluencePaper(), 0f); + Assert.assertEquals(0.9f, filter.getFillValue(), 0f); + Assert.assertEquals(ColorRGBA.Black, filter.getLineColor()); + Assert.assertEquals(4f, filter.getLineDistance(), 0f); + Assert.assertEquals(1f, filter.getLineThickness(), 0f); + Assert.assertEquals(0.9f, filter.getLuminance1(), 0f); + Assert.assertEquals(0.7f, filter.getLuminance2(), 0f); + Assert.assertEquals(0.5f, filter.getLuminance3(), 0f); + Assert.assertEquals(0.3f, filter.getLuminance4(), 0f); + Assert.assertEquals(0f, filter.getLuminance5(), 0f); + Assert.assertEquals("CrossHatchFilter", filter.getName()); + Assert.assertEquals(ColorRGBA.White, filter.getPaperColor()); + Assert.assertTrue(filter.isEnabled()); + } +} diff --git a/jme3-effects/src/test/java/com/jme3/post/filters/DepthOfFieldFilterTest.java b/jme3-effects/src/test/java/com/jme3/post/filters/DepthOfFieldFilterTest.java new file mode 100644 index 0000000000..700ba9ee77 --- /dev/null +++ b/jme3-effects/src/test/java/com/jme3/post/filters/DepthOfFieldFilterTest.java @@ -0,0 +1,94 @@ +/* + * 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.post.filters; + +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 {@code DepthOfFieldFilter} class. + * + * @author sgold + */ +public class DepthOfFieldFilterTest { + /** + * Tests serialization and de-serialization of a {@code DepthOfFieldFilter}. + * This test would've detected JME issue #2166, for instance. + */ + @Test + public void testSaveAndLoad() { + DepthOfFieldFilter filter = new DepthOfFieldFilter(); + + // Verify the default parameter values: + verifyDefaults(filter); + + // Set parameters to new values: + filter.setBlurScale(10.5f); + filter.setBlurThreshold(0.1f); + filter.setDebugUnfocus(true); + filter.setEnabled(false); + filter.setFocusDistance(66f); + filter.setFocusRange(15f); + + // Create a duplicate filter using serialization: + AssetManager assetManager = new DesktopAssetManager(); + DepthOfFieldFilter copy = BinaryExporter.saveAndLoad(assetManager, filter); + + // Verify the parameter values of the copy: + Assert.assertEquals(10.5f, copy.getBlurScale(), 0f); + Assert.assertEquals(0.1f, copy.getBlurThreshold(), 0f); + Assert.assertTrue(copy.getDebugUnfocus()); + Assert.assertEquals(66f, copy.getFocusDistance(), 0f); + Assert.assertEquals(15f, copy.getFocusRange(), 0f); + Assert.assertEquals("Depth Of Field", copy.getName()); + Assert.assertFalse(copy.isEnabled()); + } + + /** + * Verify some default values of a newly instantiated + * {@code DepthOfFieldFilter}. + * + * @param filter (not null, unaffected) + */ + private void verifyDefaults(DepthOfFieldFilter filter) { + Assert.assertEquals(1f, filter.getBlurScale(), 0f); + Assert.assertEquals(0.2f, filter.getBlurThreshold(), 0f); + Assert.assertFalse(filter.getDebugUnfocus()); + Assert.assertEquals(50f, filter.getFocusDistance(), 0f); + Assert.assertEquals(10f, filter.getFocusRange(), 0f); + Assert.assertEquals("Depth Of Field", filter.getName()); + Assert.assertTrue(filter.isEnabled()); + } +} diff --git a/jme3-effects/src/test/java/com/jme3/post/filters/FXAAFilterTest.java b/jme3-effects/src/test/java/com/jme3/post/filters/FXAAFilterTest.java new file mode 100644 index 0000000000..698efd5749 --- /dev/null +++ b/jme3-effects/src/test/java/com/jme3/post/filters/FXAAFilterTest.java @@ -0,0 +1,90 @@ +/* + * 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.post.filters; + +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 {@code FXAAFilter} class. + * + * @author sgold + */ +public class FXAAFilterTest { + /** + * Tests serialization and de-serialization of an {@code FXAAFilter}. This + * test would've detected JME issue #2168, for instance. + */ + @Test + public void testSaveAndLoad() { + FXAAFilter filter = new FXAAFilter(); + + // Verify the default parameter values: + verifyDefaults(filter); + + // Set parameters to new values: + filter.setEnabled(false); + filter.setReduceMul(0.22f); + filter.setSpanMax(7f); + filter.setSubPixelShift(0.33f); + filter.setVxOffset(0.03f); + + // Create a duplicate filter using serialization: + AssetManager assetManager = new DesktopAssetManager(); + FXAAFilter copy = BinaryExporter.saveAndLoad(assetManager, filter); + + // Verify the parameter values of the copy: + Assert.assertEquals("FXAAFilter", copy.getName()); + Assert.assertEquals(0.22f, copy.getReduceMul(), 0f); + Assert.assertEquals(7f, copy.getSpanMax(), 0f); + Assert.assertEquals(0.33f, copy.getSubPixelShift(), 0f); + Assert.assertEquals(0.03f, copy.getVxOffset(), 0f); + Assert.assertFalse(copy.isEnabled()); + } + + /** + * Verify some default values of a newly instantiated {@code FXAAFilter}. + * + * @param filter (not null, unaffected) + */ + private void verifyDefaults(FXAAFilter filter) { + Assert.assertEquals("FXAAFilter", filter.getName()); + Assert.assertEquals(0.125f, filter.getReduceMul(), 0f); + Assert.assertEquals(8f, filter.getSpanMax(), 0f); + Assert.assertEquals(0.25f, filter.getSubPixelShift(), 0f); + Assert.assertEquals(0f, filter.getVxOffset(), 0f); + Assert.assertTrue(filter.isEnabled()); + } +} diff --git a/jme3-effects/src/test/java/com/jme3/post/filters/PosterizationFilterTest.java b/jme3-effects/src/test/java/com/jme3/post/filters/PosterizationFilterTest.java new file mode 100644 index 0000000000..cef593526b --- /dev/null +++ b/jme3-effects/src/test/java/com/jme3/post/filters/PosterizationFilterTest.java @@ -0,0 +1,88 @@ +/* + * 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.post.filters; + +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 {@code PosterizationFilter} class. + * + * @author sgold + */ +public class PosterizationFilterTest { + /** + * Tests serialization and de-serialization of a + * {@code PosterizationFilter}. This test would've detected JME issue #2167, + * for instance. + */ + @Test + public void testSaveAndLoad() { + PosterizationFilter filter = new PosterizationFilter(); + + // Verify the default parameter values: + verifyDefaults(filter); + + // Set parameters to new values: + filter.setEnabled(false); + filter.setGamma(0.7f); + filter.setNumColors(4); + filter.setStrength(0.8f); + + // Create a duplicate filter using serialization: + AssetManager assetManager = new DesktopAssetManager(); + PosterizationFilter copy + = BinaryExporter.saveAndLoad(assetManager, filter); + + // Verify the parameter values of the duplicate: + Assert.assertEquals(0.7f, copy.getGamma(), 0f); + Assert.assertEquals(4, copy.getNumColors(), 0f); + Assert.assertEquals(0.8f, copy.getStrength(), 0f); + Assert.assertFalse(copy.isEnabled()); + } + + /** + * Verify some default values of a newly instantiated + * {@code PosterizationFilter}. + * + * @param filter (not null, unaffected) + */ + private void verifyDefaults(PosterizationFilter filter) { + Assert.assertEquals(0.6f, filter.getGamma(), 0f); + Assert.assertEquals(8, filter.getNumColors(), 0f); + Assert.assertEquals(1f, filter.getStrength(), 0f); + Assert.assertTrue(filter.isEnabled()); + } +} diff --git a/jme3-effects/src/test/java/com/jme3/post/filters/SSAOFilterTest.java b/jme3-effects/src/test/java/com/jme3/post/filters/SSAOFilterTest.java new file mode 100644 index 0000000000..b002ac8236 --- /dev/null +++ b/jme3-effects/src/test/java/com/jme3/post/filters/SSAOFilterTest.java @@ -0,0 +1,63 @@ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.post.ssao.SSAOFilter; +import org.junit.Assert; +import org.junit.Test; + +/** + * Automated tests for the {@code SSAOFilter} class. + * + * @author capdevon + */ +public class SSAOFilterTest { + + /** + * Tests serialization and de-serialization of an {@code SSAOFilter}. + */ + @Test + public void testSaveAndLoad() { + SSAOFilter filter = new SSAOFilter(); + + // Verify the default parameter values: + verifyDefaults(filter); + + // Set parameters to new values: + filter.setEnabled(false); + filter.setSampleRadius(4.5f); + filter.setIntensity(1.8f); + filter.setScale(0.4f); + filter.setBias(0.5f); + filter.setApproximateNormals(true); + + // Create a duplicate filter using serialization: + AssetManager assetManager = new DesktopAssetManager(); + SSAOFilter copy = BinaryExporter.saveAndLoad(assetManager, filter); + + // Verify the parameter values of the copy: + Assert.assertEquals("SSAOFilter", copy.getName()); + Assert.assertEquals(4.5f, copy.getSampleRadius(), 0f); + Assert.assertEquals(1.8f, copy.getIntensity(), 0f); + Assert.assertEquals(0.4f, copy.getScale(), 0f); + Assert.assertEquals(0.5f, copy.getBias(), 0f); + Assert.assertTrue(copy.isApproximateNormals()); + Assert.assertFalse(copy.isEnabled()); + } + + /** + * Verify some default values of a newly instantiated {@code SSAOFilter}. + * + * @param filter (not null, unaffected) + */ + private void verifyDefaults(SSAOFilter filter) { + Assert.assertEquals("SSAOFilter", filter.getName()); + Assert.assertEquals(5.1f, filter.getSampleRadius(), 0f); + Assert.assertEquals(1.5f, filter.getIntensity(), 0f); + Assert.assertEquals(0.2f, filter.getScale(), 0f); + Assert.assertEquals(0.1f, filter.getBias(), 0f); + Assert.assertFalse(filter.isApproximateNormals()); + Assert.assertTrue(filter.isEnabled()); + } +} diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index 60912ff1a8..e3a5048fc1 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -19,15 +19,17 @@ dependencies { implementation project(':jme3-effects') implementation project(':jme3-jbullet') implementation project(':jme3-jogg') - implementation project(':jme3-lwjgl') -// implementation project(':jme3-lwjgl3') + // implementation project(':jme3-lwjgl') + implementation project(':jme3-lwjgl3') implementation project(':jme3-networking') implementation project(':jme3-niftygui') implementation project(':jme3-plugins') + implementation project(':jme3-plugins-json') + implementation project(':jme3-plugins-json-gson') implementation project(':jme3-terrain') implementation project(':jme3-awt-dialogs') runtimeOnly project(':jme3-testdata') - runtimeOnly "com.github.nifty-gui:nifty-examples:${niftyVersion}" // for the "all/intro.xml" example GUI + runtimeOnly libs.nifty.examples // for the "all/intro.xml" example GUI } jar.doFirst{ diff --git a/jme3-examples/gradle.properties b/jme3-examples/gradle.properties index 5b85c3b85b..bfcfb949b5 100644 --- a/jme3-examples/gradle.properties +++ b/jme3-examples/gradle.properties @@ -2,4 +2,4 @@ assertions = true # Build javadoc per Github issue #1366 -buildJavaDoc = true \ No newline at end of file +buildJavaDoc = true diff --git a/jme3-examples/src/main/java/jme3test/TestChooser.java b/jme3-examples/src/main/java/jme3test/TestChooser.java index 28cf10064d..3799bd5832 100644 --- a/jme3-examples/src/main/java/jme3test/TestChooser.java +++ b/jme3-examples/src/main/java/jme3test/TestChooser.java @@ -72,14 +72,13 @@ 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 JFrame { - private static final Logger logger = Logger.getLogger(TestChooser.class - .getName()); + + private static final Logger logger = Logger.getLogger(TestChooser.class.getName()); private static final long serialVersionUID = 1L; @@ -105,7 +104,7 @@ public TestChooser() throws HeadlessException { @Override public void dispose() { if (executorService != null) { - executorService.shutdown(); + executorService.shutdownNow(); } super.dispose(); @@ -117,9 +116,7 @@ public void dispose() { * @return classes vector, list of all the classes in a given package (must * be found in classpath). */ - private void find(String packageName, boolean recursive, - Set> classes) { - + private void find(String packageName, boolean recursive, Set> classes) { // Translate the package name into an absolute path String name = packageName; if (!name.startsWith("/")) { @@ -148,11 +145,20 @@ private void find(String packageName, boolean recursive, try { Path directory = Paths.get(uri); - logger.log(Level.FINE, "Searching for Demo classes in \"{0}\".", directory.getFileName().toString()); + 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); + logger.logp( + Level.SEVERE, + this.getClass().toString(), + "find(pckgname, recursive, classes)", + "Exception", + e + ); } finally { if (fileSystem != null) { try { @@ -173,8 +179,7 @@ private void find(String packageName, boolean recursive, * not contain a main method */ private Class load(String name) { - String classname = name.substring(0, name.length() - - ".class".length()); + String classname = name.substring(0, name.length() - ".class".length()); if (classname.startsWith("/")) { classname = classname.substring(1); @@ -183,14 +188,16 @@ private Class load(String name) { try { final Class cls = Class.forName(classname); - cls.getMethod("main", new Class[]{String[].class}); + 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 + } 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; @@ -208,12 +215,15 @@ private Class load(String name) { * @param recursive * true to descend into subdirectories */ - private void addAllFilesInDirectory(final Path directory, - final Set> allClasses, final String packageName, final 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 try (DirectoryStream stream = Files.newDirectoryStream(directory, getFileFilter())) { for (Path file : stream) { - // we are only interested in .class files if (Files.isDirectory(file)) { if (recursive) { @@ -243,24 +253,25 @@ private void addAllFilesInDirectory(final Path directory, */ 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)); + return ( + (fileName.endsWith(".class") && (fileName.contains("Test")) && !fileName.contains("$")) || + (!fileName.startsWith(".") && Files.isDirectory(entry)) + ); } }; } 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); + JOptionPane.showMessageDialog( + rootPane, + "Please select a test from the list", + "Error", + JOptionPane.ERROR_MESSAGE + ); return; } @@ -276,7 +287,10 @@ public void run() { if (LegacyApplication.class.isAssignableFrom(clazz)) { Object app = clazz.getDeclaredConstructor().newInstance(); if (app instanceof SimpleApplication) { - final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class); + final Method settingMethod = clazz.getMethod( + "setShowSettings", + boolean.class + ); settingMethod.invoke(app, showSetting); } final Method mainMethod = clazz.getMethod("start"); @@ -296,7 +310,7 @@ public void run() { } } else { final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass()); - mainMethod.invoke(clazz, new Object[]{new String[0]}); + mainMethod.invoke(clazz, new Object[] { new String[0] }); } // wait for destroy System.gc(); @@ -309,7 +323,11 @@ public void run() { } 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); + 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(); @@ -344,31 +362,38 @@ private void setup(Collection> 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() { - @Override - 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); @@ -377,28 +402,33 @@ public void keyTyped(KeyEvent e) { okButton.setMnemonic('O'); buttonPanel.add(okButton); getRootPane().setDefaultButton(okButton); - okButton.addActionListener(new ActionListener() { - @Override - 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() { - @Override - 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 static final long serialVersionUID = 1L; private String filter; @@ -454,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 + ); } /** @@ -467,18 +499,25 @@ 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) { - 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"); - } - }); + 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); @@ -493,40 +532,47 @@ protected void addDisplayedClasses(Set> 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() { - @Override - public void removeUpdate(DocumentEvent e) { - classes.setFilter(jtf.getText()); - } + jtf + .getDocument() + .addDocumentListener( + new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + classes.setFilter(jtf.getText()); + } - @Override - public void insertUpdate(DocumentEvent e) { - classes.setFilter(jtf.getText()); - } + @Override + public void insertUpdate(DocumentEvent e) { + classes.setFilter(jtf.getText()); + } - @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); + @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() { - @Override - 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/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/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 09d034581d..da88af4ff1 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java +++ b/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java @@ -61,9 +61,11 @@ 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 diff --git a/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java b/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java index 708fe1c77a..2c5590ce63 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,50 +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 { - public static void main(String[] args) { - TestAmbient test = new TestAmbient(); - test.start(); - } + public static void main(String[] args) { + TestAmbient test = new TestAmbient(); + test.start(); + } - @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); + 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 + }; - AudioNode 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); + @Override + public void simpleInitApp() { + configureCamera(); - AudioNode 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); - } + Environment env = new Environment(eax); + audioRenderer.setEnvironment(env); - @Override - public void simpleUpdate(float tpf) { - } + 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(); + + // just a blue sphere to mark the spot + Geometry marker = makeShape("Marker", new Sphere(16, 16, 1f), ColorRGBA.Blue); + waves.attachChild(marker); + + Geometry grid = makeShape("DebugGrid", new Grid(21, 21, 2), ColorRGBA.Gray); + grid.center().move(0, 0, 0); + rootNode.attachChild(grid); + } + + private void configureCamera() { + flyCam.setMoveSpeed(25f); + flyCam.setDragToRotate(true); + + cam.setLocation(Vector3f.UNIT_XYZ.mult(5f)); + 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/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/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 aff0a2d464..e21a870993 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestReverb.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestReverb.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,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; -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; +/** + * @author capdevon + */ +public class TestReverb extends SimpleApplication implements ActionListener { + + 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 70ec211f23..a24b2f24d5 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestWav.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestWav.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 @@ -33,32 +33,107 @@ 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(); + } - public static void main(String[] args) { - TestWav test = new TestWav(); - test.start(); - } + @Override + public void simpleInitApp() { + testMaxNumChannels(); + testFakeAudio(); + testPlaySourceInstance(); - @Override - public void simpleUpdate(float tpf) { - time += tpf; - if (time > 1f) { - audioSource.playInstance(); - time = 0; + 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); + + AudioNode audio = new AudioNode(); + audio.setAudioData(data, audioKey); + return audio; + } + + /** + * 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/awt/TestCanvas.java b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java index bb4b39ac95..5f97ba8c27 100644 --- a/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java +++ b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java @@ -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; @@ -171,7 +170,7 @@ public void actionPerformed(ActionEvent e){ public void actionPerformed(ActionEvent e) { currentPanel.remove(canvas); app.stop(true); - + createCanvas(appClass); currentPanel.add(canvas, BorderLayout.CENTER); frame.pack(); diff --git a/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java b/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java index c04069ef88..b8037f4a19 100644 --- a/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java +++ b/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java @@ -45,7 +45,7 @@ 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} @@ -96,8 +96,8 @@ public void simpleInitApp() { cube2 = new Geometry("cube2", box); cube2.setMaterial(mat); - TangentBinormalGenerator.generate(cube); - TangentBinormalGenerator.generate(cube2); + MikktspaceTangentGenerator.generate(cube); + MikktspaceTangentGenerator.generate(cube2); batch.attachChild(cube); // batch.attachChild(cube2); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java b/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java index a265bdba96..a607878185 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java @@ -31,38 +31,6 @@ */ package jme3test.bullet; -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - import com.jme3.app.SimpleApplication; import com.jme3.asset.TextureKey; import com.jme3.bullet.BulletAppState; 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/collision/TestMousePick.java b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java index 9ff54ae532..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,102 +29,166 @@ * 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(); } - + + private BitmapText hud; private Node shootables; private Geometry mark; @Override public void simpleInitApp() { - flyCam.setEnabled(false); + hud = createLabel(10, 10, "Text"); + configureCamera(); initMark(); + setupScene(); + setupLights(); + } + + private void configureCamera() { + flyCam.setMoveSpeed(15f); + flyCam.setDragToRotate(true); + 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 */ - 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); - 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. */ - private 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; } /** @@ -132,22 +196,10 @@ private Geometry makeFloor() { */ private void initMark() { Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f)); - mark = new Geometry("BOOM!", arrow); - Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - 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); } - 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); - - // 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/effect/TestEverything.java b/jme3-examples/src/main/java/jme3test/effect/TestEverything.java index b579a03601..bb5ab62a1a 100644 --- a/jme3-examples/src/main/java/jme3test/effect/TestEverything.java +++ b/jme3-examples/src/main/java/jme3test/effect/TestEverything.java @@ -48,7 +48,7 @@ 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 { @@ -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/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/helloworld/HelloMaterial.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloMaterial.java index e12fef4569..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. */ @@ -81,7 +81,7 @@ public void simpleInitApp() { 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")); diff --git a/jme3-examples/src/main/java/jme3test/input/TestJoystick.java b/jme3-examples/src/main/java/jme3test/input/TestJoystick.java index 3f25532d50..6ef8435d98 100644 --- a/jme3-examples/src/main/java/jme3test/input/TestJoystick.java +++ b/jme3-examples/src/main/java/jme3test/input/TestJoystick.java @@ -46,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(); } @@ -155,7 +157,7 @@ protected void setViewedJoystick( Joystick stick ) { } } - + /** * Easier to watch for all button and axis events with a raw input listener. */ @@ -184,6 +186,7 @@ public void onJoyAxisEvent(JoyAxisEvent evt) { gamepad.setAxisValue( evt.getAxis(), value ); if( value != 0 ) { lastValues.put(evt.getAxis(), value); + evt.getAxis().getJoystick().rumble(0.5f); } } @@ -191,6 +194,7 @@ public void onJoyAxisEvent(JoyAxisEvent evt) { public void onJoyButtonEvent(JoyButtonEvent evt) { setViewedJoystick( evt.getButton().getJoystick() ); gamepad.setButtonValue( evt.getButton(), evt.isPressed() ); + evt.getButton().getJoystick().rumble(1f); } @Override @@ -255,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 // @@ -280,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(); } @@ -295,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) ) { + } 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; @@ -318,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; diff --git a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java index 81a268fdf4..0c6f437305 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java +++ b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java @@ -58,7 +58,7 @@ 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 { @@ -108,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); 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/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 857d4374fe..600e60cc95 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java +++ b/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java @@ -48,7 +48,7 @@ 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 { @@ -84,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); diff --git a/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java b/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java index 535db645d3..dba24ad18d 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java @@ -43,7 +43,7 @@ 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 { @@ -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 e65c1763da..ed53b0de35 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java @@ -43,7 +43,7 @@ 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 { @@ -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); } diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java index 3c6c653102..11dd519ef3 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java @@ -49,7 +49,7 @@ 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 { @@ -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); @@ -149,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); @@ -166,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); } diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentCube.java b/jme3-examples/src/main/java/jme3test/light/TestTangentCube.java index 13841f986f..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", diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java index 80fd44084c..2337c0bca9 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java @@ -45,7 +45,9 @@ 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; @@ -79,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"); @@ -89,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); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java index 9121d09154..e791d30889 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java @@ -42,7 +42,8 @@ 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 { @@ -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 f4cd8c4215..d3201cce99 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java @@ -44,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; /** @@ -120,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 b013dc6c1d..16229161d9 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java @@ -50,7 +50,7 @@ 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 { @@ -72,7 +72,7 @@ public void simpleInitApp() { new Vector3f(-10, 0, -10) ); rm.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(10)); - TangentBinormalGenerator.generate(rm); + MikktspaceTangentGenerator.generate(rm); Geometry geom = new Geometry("floor", rm); Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTwoSideLighting.java b/jme3-examples/src/main/java/jme3test/light/TestTwoSideLighting.java index e5dfdc754c..476c261ef6 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTwoSideLighting.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTwoSideLighting.java @@ -44,7 +44,7 @@ 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. @@ -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/pbr/TestPBRLighting.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java index 7de1e456c7..50fb4a482b 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java @@ -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(); @@ -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); - } - }); + 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/material/TestBumpModel.java b/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java index f71e43ea63..645dd074f5 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java +++ b/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java @@ -42,7 +42,7 @@ 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 { @@ -59,7 +59,7 @@ public static void main(String[] args){ public void simpleInitApp() { Spatial signpost = assetManager.loadAsset(new OgreMeshKey("Models/Sign Post/Sign Post.mesh.xml")); signpost.setMaterial(assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m")); - TangentBinormalGenerator.generate(signpost); + MikktspaceTangentGenerator.generate(signpost); rootNode.attachChild(signpost); lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); diff --git a/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java b/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java index e75b33ba58..e8ca464d7e 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java +++ b/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java @@ -41,7 +41,7 @@ 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 { @@ -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 80afeaf382..3715806f6d 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestParallax.java +++ b/jme3-examples/src/main/java/jme3test/material/TestParallax.java @@ -44,7 +44,7 @@ import com.jme3.scene.Spatial; 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 { @@ -77,7 +77,7 @@ public void setupFloor() { rm.scaleTextureCoordinates(new Vector2f(10, 10)); Geometry floorGeom = new Geometry("floorGeom", rm); - TangentBinormalGenerator.generate(floorGeom); + MikktspaceTangentGenerator.generate(floorGeom); floorGeom.setMaterial(mat); rootNode.attachChild(floorGeom); @@ -86,7 +86,7 @@ public void setupFloor() { 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); diff --git a/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java b/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java index aca2282634..038066c68b 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java +++ b/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java @@ -44,7 +44,7 @@ import com.jme3.scene.Spatial; 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 { @@ -80,7 +80,7 @@ public void setupFloor() { rm.scaleTextureCoordinates(new Vector2f(10, 10)); Geometry floorGeom = new Geometry("floorGeom", rm); - TangentBinormalGenerator.generate(floorGeom); + MikktspaceTangentGenerator.generate(floorGeom); //floorGeom.setLocalScale(100); floorGeom.setMaterial(mat); @@ -90,7 +90,7 @@ public void setupFloor() { 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); diff --git a/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java b/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java index 4185e1caf7..6fb1f5037f 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java +++ b/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java @@ -42,7 +42,7 @@ 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 { @@ -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/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/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/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java index bbf03a8348..4ece681a65 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java @@ -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; @@ -81,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)); @@ -133,7 +135,7 @@ 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/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); @@ -152,10 +154,14 @@ public void simpleInitApp() { // 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); @@ -228,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) { 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/anim/TestAnimationFactory.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java index e09500e248..602b88d04a 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java @@ -12,7 +12,7 @@ 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 { @@ -45,7 +45,7 @@ public void simpleInitApp() { childModel.setLocalTranslation(2, 2, 2); childModel.attachChild(childGeom); model.attachChild(childModel); - TangentBinormalGenerator.generate(model); + MikktspaceTangentGenerator.generate(model); // Construct a complex animation using AnimFactory: // 6 seconds in duration, named "anim", running at 25 frames per second 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/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/post/TestBloom.java b/jme3-examples/src/main/java/jme3test/post/TestBloom.java index faef6ac8e7..5608890801 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestBloom.java +++ b/jme3-examples/src/main/java/jme3test/post/TestBloom.java @@ -75,8 +75,6 @@ public void simpleInitApp() { mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); - - Material matSoil = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); matSoil.setFloat("Shininess", 15f); diff --git a/jme3-examples/src/main/java/jme3test/post/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/TestPostFilters.java b/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java index b7f6658ce7..a46b14655c 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java +++ b/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java @@ -48,7 +48,7 @@ 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 { @@ -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); 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/TestTransparentSSAO.java b/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java index 6694454a9b..241796d444 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java +++ b/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java @@ -12,7 +12,7 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.scene.shape.RectangleMesh; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestTransparentSSAO extends SimpleApplication { @@ -42,7 +42,7 @@ public void simpleInitApp() { geom.setMaterial(mat); 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/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/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/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/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/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/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/TestLodGeneration.java b/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java index f182460251..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-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,10 +44,10 @@ 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.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; @@ -58,44 +58,48 @@ 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(); } - private boolean wireFrame = false; + 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 BitmapText hudText; - final private List listGeoms = new ArrayList<>(); - final private ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5); + 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 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); + + // 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); @@ -103,11 +107,17 @@ public void simpleInitApp() { 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); } + // --- Initial LOD Generation --- + // Set initial reduction value and LOD level reductionValue = 0.80f; lodLevel = 1; + + // Generate LODs for each geometry in the model for (final Geometry geom : listGeoms) { LodGenerator lodGenerator = new LodGenerator(geom); lodGenerator.bakeLods(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionValue); @@ -115,45 +125,49 @@ public void simpleInitApp() { } 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"); + // --- HUD Setup --- hudText = new BitmapText(guiFont); - hudText.setSize(guiFont.getCharSet().getRenderedSize()); 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() { - @Override - public void onAction(String name, boolean isPressed, float tpf) { - if (isPressed) { - if (name.equals("plus")) { - reductionValue += 0.05f; - updateLod(); - } - if (name.equals("minus")) { - reductionValue -= 0.05f; - updateLod(); - } - if (name.equals("wireFrame")) { - wireFrame = !wireFrame; - for (Geometry geom : listGeoms) { - Material mat = geom.getMaterial(); - mat.getAdditionalRenderState().setWireframe(wireFrame); - } - } - } + // Register input mappings for user interaction + registerInputMappings(); + } + + @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)); } - @Override - public void simpleUpdate(float tpf) { + private void addMapping(String mappingName, Trigger... triggers) { + inputManager.addMapping(mappingName, triggers); + inputManager.addListener(this, mappingName); } @Override @@ -163,14 +177,20 @@ public void destroy() { } 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 geom : listGeoms) { Mesh mesh = geom.getMesh(); + // Check if the mesh has LOD levels if (mesh.getNumLodLevels() > 0) { nbTri += mesh.getLodLevel(lodLevel).getNumElements(); } else { @@ -180,24 +200,46 @@ private int computeNbTri() { return nbTri; } - 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 geom : listGeoms) { LodGenerator lodGenerator = new LodGenerator(geom); - final VertexBuffer[] lods = lodGenerator.computeLods(method, value); + 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 { geom.getMesh().setLodLevels(lods); + + // Reset lodLevel to 0 initially lodLevel = 0; - if (geom.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; } geom.setLodLevel(lodLevel); - hudText.setText(computeNbTri() + " tris"); + + 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/terrain/PBRTerrainAdvancedTest.java b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java index 8375db317a..d4cd04e403 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,6 +52,8 @@ import com.jme3.texture.Image; import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.TextureArray; import java.util.ArrayList; import java.util.List; @@ -187,6 +189,20 @@ public void onAction(String name, boolean pressed, float tpf) { 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){ + + } + } + } } }; @@ -279,10 +295,10 @@ private void setUpTerrainMaterial() { TextureArray metallicRoughnessAoEiTextureArray = new TextureArray(metallicRoughnessAoEiMapImages); //apply wrapMode to the whole texture array, rather than each individual texture in the array - albedoTextureArray.setWrap(WrapMode.Repeat); - normalParallaxTextureArray.setWrap(WrapMode.Repeat); - metallicRoughnessAoEiTextureArray.setWrap(WrapMode.Repeat); - + setWrapAndMipMaps(albedoTextureArray); + setWrapAndMipMaps(normalParallaxTextureArray); + setWrapAndMipMaps(metallicRoughnessAoEiTextureArray); + //assign texture array to materials matTerrain.setParam("AlbedoTextureArray", VarType.TextureArray, albedoTextureArray); matTerrain.setParam("NormalParallaxTextureArray", VarType.TextureArray, normalParallaxTextureArray); @@ -362,16 +378,51 @@ private void setupKeys() { 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) \nPress 'P' to toggle tri-planar mode"); + 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); @@ -430,6 +481,12 @@ private void setUpTerrain() { rootNode.attachChild(terrain); } + private void setWrapAndMipMaps(Texture texture){ + texture.setWrap(WrapMode.Repeat); + texture.setMinFilter(MinFilter.Trilinear); + texture.setMagFilter(MagFilter.Bilinear); + } + private void setUpLights() { LightProbe probe = (LightProbe) assetManager.loadAsset("Scenes/LightProbes/quarry_Probe.j3o"); @@ -444,7 +501,7 @@ private void setUpLights() { rootNode.addLight(directionalLight); ambientLight = new AmbientLight(); - directionalLight.setColor(ColorRGBA.White); + ambientLight.setColor(ColorRGBA.White); rootNode.addLight(ambientLight); } diff --git a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java index db17e0da26..1500257612 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java @@ -1,7 +1,7 @@ package jme3test.terrain; /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -153,6 +153,22 @@ public void onAction(String name, boolean pressed, float tpf) { 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){ + + } + } + + } + } }; @@ -270,14 +286,49 @@ private void setupKeys() { 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) \nPress 'P' to toggle tri-planar mode"); + 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 @@ -352,7 +403,7 @@ private void setUpLights() { rootNode.addLight(directionalLight); ambientLight = new AmbientLight(); - directionalLight.setColor(ColorRGBA.White); + ambientLight.setColor(ColorRGBA.White); rootNode.addLight(ambientLight); } diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java index 7cf0eb82f9..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; @@ -28,7 +26,6 @@ import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; -import java.io.File; public class TerrainGridTileLoaderTest extends SimpleApplication { @@ -46,15 +43,11 @@ public static void main(final String[] args) { @Override public void simpleInitApp() { - File file = new File("TerrainGridTestData.zip"); - if (!file.exists()) { - 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); - } - + /* + * 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); @@ -121,7 +114,7 @@ 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)); 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/water/TestPostWater.java b/jme3-examples/src/main/java/jme3test/water/TestPostWater.java index 384d88f7f9..7f6117bc0c 100644 --- a/jme3-examples/src/main/java/jme3test/water/TestPostWater.java +++ b/jme3-examples/src/main/java/jme3test/water/TestPostWater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ 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; @@ -55,7 +56,9 @@ import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Node; import com.jme3.scene.Spatial; +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; @@ -66,18 +69,12 @@ import com.jme3.water.WaterFilter; /** - * test - * * @author normenhansen */ public class TestPostWater extends SimpleApplication { - final 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; - private AudioNode waves; - final private LowPassFilter aboveWaterAudioFilter = new LowPassFilter(1, 1); - final private Filter underWaterAudioFilter = new LowPassFilter(0.5f, 0.1f); - private boolean useDryFilter = true; public static void main(String[] args) { TestPostWater app = new TestPostWater(); @@ -87,173 +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); - updateAudio(); - audioRenderer.playSource(waves); - // viewPort.addProcessor(fpp); + } - 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"); - - inputManager.addListener(new ActionListener() { - @Override - 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()); - } - if (name.equals("dryFilter")) { - useDryFilter = !useDryFilter; - } - } - } - }, "foam1", "foam2", "foam3", "upRM", "downRM", "dryFilter"); - 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("dryFilter", new KeyTrigger(KeyInput.KEY_4)); - inputManager.addMapping("upRM", new KeyTrigger(KeyInput.KEY_PGUP)); - inputManager.addMapping("downRM", new KeyTrigger(KeyInput.KEY_PGDN)); + 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 createTerrain(Node rootNode) { - 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); + private void createTerrain(Node mainScene) { + Material matRock = createTerrainMaterial(); + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); AbstractHeightMap heightmap = null; try { heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); @@ -261,51 +239,86 @@ private void createTerrain(Node rootNode) { } catch (Exception e) { e.printStackTrace(); } - TerrainQuad terrain - = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + + 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 varying 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; - final 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); - uw = water.isUnderWater(); + underWater = water.isUnderWater(); updateAudio(); } - - protected void setText(int x, int y, String text) { - BitmapText txt2 = new BitmapText(guiFont); - txt2.setText(text); - txt2.setLocalTranslation(x, cam.getHeight() - y, 0); - txt2.setColor(ColorRGBA.Red); - guiNode.attachChild(txt2); + + private void addAudioClip() { + underWater = cam.getLocation().y < waterHeight; + + 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 uw} and {@code useDryFilter}). + * based on boolean fields ({@code underWater} and {@code useDryFilter}). */ - protected void updateAudio() { + private void updateAudio() { Filter newDryFilter; if (!useDryFilter) { newDryFilter = null; - } else if (uw) { + } else if (underWater) { newDryFilter = underWaterAudioFilter; } else { newDryFilter = aboveWaterAudioFilter; @@ -316,11 +329,11 @@ protected void updateAudio() { waves.setDryFilter(newDryFilter); } - boolean newReverbEnabled = !uw; + boolean newReverbEnabled = !underWater; boolean oldReverbEnabled = waves.isReverbEnabled(); if (oldReverbEnabled != newReverbEnabled) { System.out.println("reverb enabled : " + newReverbEnabled); waves.setReverbEnabled(newReverbEnabled); } } -} \ No newline at end of file +} 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 index 264aaf7015..7827bbea96 100644 --- a/jme3-examples/src/main/resources/jme3test/materials/TestIssue37.j3md +++ b/jme3-examples/src/main/resources/jme3test/materials/TestIssue37.j3md @@ -24,8 +24,8 @@ MaterialDef TestIssue37 { } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/ShowNormals.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Misc/ShowNormals.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Misc/ShowNormals.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Misc/ShowNormals.frag WorldParameters { WorldViewProjectionMatrix 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,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" + }, + { + "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/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/src/main/java/com/jme3/renderer/ios/IosGL.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java index dcef2a64a6..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 @@ -207,6 +207,11 @@ 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); @@ -789,5 +794,20 @@ public void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int 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/system/ios/IGLESContext.java b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java index 009195c776..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 @@ -42,6 +42,8 @@ import com.jme3.renderer.ios.IosGL; 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; @@ -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 @@ -123,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 @@ -143,8 +153,7 @@ public Timer getTimer() { } @Override - public void setTitle(String title) { - } + public void setTitle(String title) {} @Override public boolean isCreated() { @@ -160,7 +169,7 @@ public void setAutoFlushFrames(boolean enabled) { @Override public boolean isRenderable() { logger.log(Level.FINE, "IGLESContext isRenderable"); - return true;// renderable.get(); + return true; // renderable.get(); } @Override @@ -169,18 +178,18 @@ public void create(boolean waitFor) { IosGL gl = new IosGL(); if (settings.getBoolean("GraphicsDebug")) { - gl = (IosGL)GLDebug.createProxy(gl, gl, GL.class, GLExt.class, GLFbo.class); + gl = (IosGL) GLDebug.createProxy(gl, gl, GL.class, GLExt.class, GLFbo.class); } 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(); @@ -196,8 +205,7 @@ public void create() { } @Override - public void restart() { - } + public void restart() {} @Override public void destroy(boolean waitFor) { @@ -217,8 +225,7 @@ protected void waitFor(boolean createdVal) { while (renderable.get() != createdVal) { try { Thread.sleep(10); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } } @@ -267,4 +274,16 @@ public int getWindowXPosition() { public int getWindowYPosition() { throw new UnsupportedOperationException("not implemented yet"); } -} \ No newline at end of file + + @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/JmeIosSystem.java b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java index 77f41c1f82..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,7 +35,8 @@ import com.jme3.system.JmeContext; import com.jme3.system.JmeSystemDelegate; import com.jme3.system.NullContext; -import com.jme3.util.functional.VoidFunction; +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; @@ -64,7 +65,7 @@ public JmeIosSystem() { @Override public URL getPlatformAssetConfigURL() { - return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/IOS.cfg"); + return Resources.getResource("com/jme3/asset/IOS.cfg"); } @Override @@ -108,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/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-jbullet/build.gradle b/jme3-jbullet/build.gradle index 1a2621a420..eb6ce8af88 100644 --- a/jme3-jbullet/build.gradle +++ b/jme3-jbullet/build.gradle @@ -14,8 +14,8 @@ sourceSets { } dependencies { - api 'com.github.stephengold:jbullet:1.0.2' - api 'javax.vecmath:vecmath:1.5.2' + api libs.jbullet + api libs.vecmath api project(':jme3-core') api project(':jme3-terrain') compileOnly project(':jme3-vr') //is selectively used if on classpath diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java index b8df7695ff..9bbfb86f5c 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java @@ -32,7 +32,6 @@ package com.jme3.bullet.debug; import com.jme3.app.Application; -import com.jme3.app.VRAppState; import com.jme3.app.state.AbstractAppState; import com.jme3.app.state.AppStateManager; import com.jme3.asset.AssetManager; @@ -173,17 +172,11 @@ public void initialize(AppStateManager stateManager, Application app) { setupMaterials(app); physicsDebugRootNode.setCullHint(Spatial.CullHint.Never); - if (isVr()) { - /* This is a less good solution than the non-vr version (as the debug shapes can be obscured by the regular - * geometry), however it is the best possible as VR does not currently support multiple viewports per eye */ - VRAppState vrAppState = stateManager.getState(VRAppState.ID, VRAppState.class); - vrAppState.getLeftViewPort().attachScene(physicsDebugRootNode); - vrAppState.getRightViewPort().attachScene(physicsDebugRootNode); - } else { - viewPort = rm.createMainView("Physics Debug Overlay", app.getCamera()); - viewPort.setClearFlags(false, true, false); - viewPort.attachScene(physicsDebugRootNode); - } + + viewPort = rm.createMainView("Physics Debug Overlay", app.getCamera()); + viewPort.setClearFlags(false, true, false); + viewPort.attachScene(physicsDebugRootNode); + } /** @@ -193,15 +186,8 @@ public void initialize(AppStateManager stateManager, Application app) { * is invoked. */ @Override - public void cleanup() { - if (isVr()) { - VRAppState vrAppState = app.getStateManager().getState(VRAppState.ID, VRAppState.class); - vrAppState.getLeftViewPort().detachScene(physicsDebugRootNode); - vrAppState.getRightViewPort().detachScene(physicsDebugRootNode); - } else { - rm.removeMainView(viewPort); - } - + public void cleanup() { + rm.removeMainView(viewPort); super.cleanup(); } @@ -436,17 +422,4 @@ public static interface DebugAppStateFilter { */ public boolean displayObject(Object obj); } - - private boolean isVr() { - if (isVr == null) { - try { - VRAppState vrAppState = app.getStateManager().getState(VRAppState.ID, VRAppState.class); - isVr = vrAppState != null && !vrAppState.DISABLE_VR; - } catch (NoClassDefFoundError e) { - //Vr isn't even on the classpath - isVr = false; - } - } - return isVr; - } -} \ No newline at end of file +} diff --git a/jme3-jogg/build.gradle b/jme3-jogg/build.gradle index a039b96bca..7bf0052577 100644 --- a/jme3-jogg/build.gradle +++ b/jme3-jogg/build.gradle @@ -1,4 +1,4 @@ dependencies { api project(':jme3-core') - api 'com.github.stephengold:j-ogg-vorbis:1.0.3' + api libs.j.ogg.vorbis } diff --git a/jme3-lwjgl/build.gradle b/jme3-lwjgl/build.gradle index 846c2fd254..b0f5a08d3b 100644 --- a/jme3-lwjgl/build.gradle +++ b/jme3-lwjgl/build.gradle @@ -2,13 +2,12 @@ dependencies { api project(':jme3-core') api project(':jme3-desktop') - api 'org.jmonkeyengine:lwjgl:2.9.5' - runtimeOnly 'org.jmonkeyengine:lwjgl-platform:2.9.5' + api libs.lwjgl2 /* * Upgrades the default jinput-2.0.5 to jinput-2.0.9 to fix a bug with gamepads on Linux. * See https://hub.jmonkeyengine.org/t/linux-gamepad-input-on-jme3-lwjgl-splits-input-between-two-logical-gamepads */ - api 'net.java.jinput:jinput:2.0.9' - api 'net.java.jinput:jinput:2.0.9:natives-all' + api libs.jinput + api(variantOf(libs.jinput){ classifier('natives-all') }) } \ No newline at end of file diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 2dcf289584..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.*; @@ -59,7 +60,40 @@ public void glBindBuffer(int param1, int param2) { 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); @@ -98,6 +132,12 @@ public void glBufferData(int param1, ByteBuffer param2, int param3) { 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); @@ -286,6 +326,12 @@ public void glGetBufferSubData(int target, long offset, ByteBuffer 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(); 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 303136a1ef..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 @@ -132,7 +132,10 @@ public void uncaughtException(Thread thread, Throwable thrown) { } listener.handleError("Failed to create display", ex); - createdLock.notifyAll(); // Release the lock, so start(true) doesn't deadlock. + synchronized (createdLock) { + createdLock.notifyAll(); // Release the lock, so start(true) doesn't deadlock. + } + return false; // if we failed to create display, do not continue } 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 0bfb63976e..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,505 +1,519 @@ -/* - * 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.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?) - } - } -} +/* + * 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/LwjglDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java index 1db33942e8..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,285 +1,325 @@ -/* - * 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.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); - } - -} +/* + * 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 5484d13cf6..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,221 +1,233 @@ -/* - * 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 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) { - } - -} +/* + * 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-lwjgl3/build.gradle b/jme3-lwjgl3/build.gradle index c3cb630545..e360ab7054 100644 --- a/jme3-lwjgl3/build.gradle +++ b/jme3-lwjgl3/build.gradle @@ -1,56 +1,66 @@ dependencies { api project(':jme3-core') + api project(':jme3-desktop') + api (libs.lwjgl3.awt) { + exclude group: 'org.lwjgl', module: 'lwjgl' + } + + 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 + + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-macos-arm64') }) - api "org.lwjgl:lwjgl:${lwjgl3Version}" - api "org.lwjgl:lwjgl-glfw:${lwjgl3Version}" - api "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}" - api "org.lwjgl:lwjgl-openal:${lwjgl3Version}" - api "org.lwjgl:lwjgl-opencl:${lwjgl3Version}" - api "org.lwjgl:lwjgl-opengl:${lwjgl3Version}" - - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-macos-arm64" - - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-macos-arm64" - - - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-macos-arm64" - - - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-macos-arm64" - - - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-macos-arm64" + 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 { 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 39f0fa6b5a..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,43 +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); - joysticks.put(i, joystick); - - final FloatBuffer floatBuffer = glfwGetJoystickAxes(i); - - int axisIndex = 0; - while (floatBuffer.hasRemaining()) { - floatBuffer.get(); - - final String logicalId = JoystickCompatibilityMappings.remapAxis(joystick.getName(), convertAxisIndex(axisIndex)); - final JoystickAxis joystickAxis = new DefaultJoystickAxis(inputManager, joystick, axisIndex, convertAxisIndex(axisIndex), logicalId, true, false, 0.0f); - joystick.addAxis(axisIndex, joystickAxis); - axisIndex++; + 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) }); } - final ByteBuffer byteBuffer = glfwGetJoystickButtons(i); + GlfwJoystick joystick = new GlfwJoystick(inputManager, this, i, name, isGlfwGamepad); + joysticks.put(i, joystick); - if (byteBuffer != null) { - int buttonIndex = 0; - while (byteBuffer.hasRemaining()) { - byteBuffer.get(); + 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++; + } - final String logicalId = JoystickCompatibilityMappings.remapButton(joystick.getName(), String.valueOf(buttonIndex)); - final JoystickButton button = new DefaultJoystickButton(inputManager, joystick, buttonIndex, String.valueOf(buttonIndex), logicalId); + // 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); + } + + // 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); - 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) { @@ -136,52 +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() { float rawValue, value; - for (final Map.Entry entry : joysticks.entrySet()) { + 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()); - // Axes - final FloatBuffer axisValues = glfwGetJoystickAxes(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(); - // 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. + FloatBuffer axes = gamepadState.axes(); - if (axisValues != null) { - for (final JoystickAxis axis : entry.getValue().getAxes()) { - rawValue = axisValues.get(axis.getAxisId()); - value = JoystickCompatibilityMappings.remapAxisRange(axis, rawValue); - listener.onJoyAxisEvent(new JoyAxisEvent(axis, value, rawValue)); + // 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; + + rawValue = axes.get(axisId); + rawValue = remapAxisValueToJme(axisId, rawValue); + value = rawValue; // scaling handled by GLFW + + updateAxis(axis, value, rawValue); } - } - // Buttons - final ByteBuffer byteBuffer = glfwGetJoystickButtons(entry.getKey()); + // 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); + } - if (byteBuffer != null) { - for (final JoystickButton button : entry.getValue().getButtons()) { - final boolean pressed = byteBuffer.get(button.getButtonId()) == GLFW_PRESS; + ByteBuffer buttons = gamepadState.buttons(); + + for (int btnIndex = 0; btnIndex <= GLFW_GAMEPAD_BUTTON_LAST; btnIndex++) { + String jmeButtonIndex = remapButtonToJme(btnIndex); + if (jmeButtonIndex == null) continue; - if (joyButtonPressed.get(button) != pressed) { - joyButtonPressed.put(button, pressed); - listener.onJoyButtonEvent(new JoyButtonEvent(button, pressed)); - } + JoystickButton button = joystick.getButton(jmeButtonIndex); + if (button == null) continue; + + 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; @@ -199,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 @@ -248,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/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/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/LwjglPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglPlatform.java index 6d4a8e64a7..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 @@ -78,7 +78,7 @@ public List getDevices() { * @param deviceType the device type * @return the available devices */ - private long[] getDevices(int deviceType) { + private long[] getDevices(long deviceType) { int[] count = new int[1]; int errcode = CL10.clGetDeviceIDs(platform, deviceType, null, count); if (errcode == CL10.CL_DEVICE_NOT_FOUND) { 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 6c070c83da..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 @@ -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) { @@ -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,6 +353,12 @@ 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(); @@ -653,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/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 0a0a6bcd54..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 @@ -32,6 +32,7 @@ package com.jme3.system.lwjgl; +import com.jme3.input.JoyInput; import com.jme3.input.lwjgl.GlfwJoystickInput; import com.jme3.input.lwjgl.GlfwKeyInput; import com.jme3.input.lwjgl.GlfwMouseInput; @@ -124,7 +125,7 @@ public abstract class LwjglContext implements JmeContext { protected GlfwKeyInput keyInput; protected GlfwMouseInput mouseInput; - protected GlfwJoystickInput joyInput; + protected JoyInput joyInput; protected Timer timer; @@ -275,17 +276,18 @@ private void initContext(boolean first) { 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. - - if ( event == GLFW.GLFW_CONNECTED ) { - joyInput.reloadJoysticks(); - joyInput.fireJoystickConnectedEvent(jid); - } - else { - joyInput.fireJoystickDisconnectedEvent(jid); - joyInput.reloadJoysticks(); + GlfwJoystickInput glfwJoyInput = (GlfwJoystickInput) joyInput; + + if (event == GLFW.GLFW_CONNECTED) { + glfwJoyInput.reloadJoysticks(); + glfwJoyInput.fireJoystickConnectedEvent(jid); + } else { + glfwJoyInput.fireJoystickDisconnectedEvent(jid); + glfwJoyInput.reloadJoysticks(); } } }); 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 1647540ffb..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-2023 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,22 +42,15 @@ 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 com.jme3.util.SafeArrayList; -import org.lwjgl.Version; -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; - import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; @@ -64,10 +60,15 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; - -import static org.lwjgl.glfw.GLFW.*; -import static org.lwjgl.opengl.GL11.GL_FALSE; -import static org.lwjgl.system.MemoryUtil.NULL; +import org.lwjgl.PointerBuffer; +import org.lwjgl.Version; +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. @@ -79,60 +80,99 @@ 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_OPENGL31, () -> { - 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); - }); + 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 final SafeArrayList windowSizeListeners = new SafeArrayList<>( + WindowSizeListener.class + ); private GLFWErrorCallback errorCallback; private GLFWWindowSizeCallback windowSizeCallback; @@ -141,6 +181,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { private Thread mainThread; + private long monitor = NULL; private long window = NULL; private int frameRateLimit = -1; @@ -149,8 +190,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { protected boolean allowSwapBuffers = false; // temp variables used for glfw calls - private int width[] = new int[1]; - private int height[] = new int[1]; + private final int width[] = new int[1]; + private final int height[] = new int[1]; // state maintained by updateSizes() private int oldFramebufferWidth; @@ -158,7 +199,6 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { 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"); } @@ -222,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"); @@ -241,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); @@ -262,8 +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()<=0?GLFW_DONT_CARE:settings.getFrequency()); - glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, settings.isUseRetinaFrameBuffer() ? GLFW_TRUE : GLFW_FALSE); + 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); @@ -277,53 +347,85 @@ 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()); + final GLFWVidMode videoMode = glfwGetVideoMode(monitor); int requestWidth = settings.getWindowWidth(); int requestHeight = settings.getWindowHeight(); if (requestWidth <= 0 || requestHeight <= 0) { requestWidth = videoMode.width(); requestHeight = videoMode.height(); } - window = glfwCreateWindow(requestWidth, requestHeight, settings.getTitle(), monitor, NULL); + 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); + } if (window == NULL) { throw new RuntimeException("Failed to create the GLFW window"); } - 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; } - } - }); + ); - if (!settings.isFullscreen()) { - if (settings.getCenterWindow()) { - // Center the window - glfwSetWindowPos(window, - (videoMode.width() - requestWidth) / 2, - (videoMode.height() - requestHeight) / 2); + 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 { - glfwSetWindowPos(window, - settings.getWindowXPosition(), - settings.getWindowYPosition()); + LOGGER.log(Level.WARNING, "Can't change platform to X11 (GLX), check if you have XWayland enabled"); } } + + 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 glfwMakeContextCurrent(window); @@ -341,24 +443,30 @@ public void invoke(final long window, final boolean focus) { // 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(); - } - }); + // 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(); - } - }); + glfwSetFramebufferSizeCallback( + window, + framebufferSizeCallback = + new GLFWFramebufferSizeCallback() { + @Override + public void invoke(final long window, final int width, final int height) { + updateSizes(); + } + } + ); allowSwapBuffers = settings.isSwapBuffers(); @@ -376,8 +484,7 @@ private void updateSizes() { 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) { + if (settings.getWindowWidth() != windowWidth || settings.getWindowHeight() != windowHeight) { settings.setWindowSize(windowWidth, windowHeight); for (WindowSizeListener wsListener : windowSizeListeners.getArray()) { wsListener.onWindowSizeChanged(windowWidth, windowHeight); @@ -387,8 +494,7 @@ private void updateSizes() { glfwGetFramebufferSize(window, width, height); int framebufferWidth = width[0]; int framebufferHeight = height[0]; - if (framebufferWidth != oldFramebufferWidth - || framebufferHeight != oldFramebufferHeight) { + if (framebufferWidth != oldFramebufferWidth || framebufferHeight != oldFramebufferHeight) { settings.setResolution(framebufferWidth, framebufferHeight); listener.reshape(framebufferWidth, framebufferHeight); @@ -415,6 +521,10 @@ protected void showWindow() { * @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; @@ -422,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); @@ -436,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++) { @@ -451,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(); @@ -496,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); @@ -523,7 +632,6 @@ protected void destroyContext() { glfwDestroyWindow(window); window = NULL; } - } catch (final Exception ex) { listener.handleError("Failed to destroy context", ex); } @@ -551,7 +659,6 @@ public void create(boolean waitFor) { waitFor(true); } } - } /** @@ -563,14 +670,16 @@ 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(); @@ -604,7 +713,6 @@ protected boolean initInThread() { return true; } - /** * execute one iteration of the render loop in the OpenGL thread */ @@ -618,16 +726,16 @@ protected void runLoop() { throw new IllegalStateException(); } - listener.update(); // All this does is call glfwSwapBuffers(). // 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) { + // 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) { @@ -695,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()); @@ -707,7 +816,6 @@ public void run() { } while (true) { - runLoop(); if (needClose.get()) { @@ -725,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; } @@ -847,4 +967,80 @@ public int getWindowYPosition() { 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 index db9beef520..3161f00a9b 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/Sync.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/Sync.java @@ -31,7 +31,6 @@ */ package com.jme3.system.lwjgl; - /** * A highly accurate sync method that continually adapts to the system * it runs on to provide reliable results. @@ -41,8 +40,6 @@ */ class Sync { - - /** number of nanoseconds in a second */ private static final long NANOS_IN_SECOND = 1000L * 1000L * 1000L; 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-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/MacOSXGLPlatform.java similarity index 70% rename from jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java rename to jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/MacOSXGLPlatform.java index 5df00bf08f..bce9b4c6da 100644 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/MacOSXGLPlatform.java @@ -1,54 +1,56 @@ -/* - * 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; - } -} +/* + * 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 75e27c5400..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; /** @@ -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-niftygui/build.gradle b/jme3-niftygui/build.gradle index 088cb562ef..b11c3e5017 100644 --- a/jme3-niftygui/build.gradle +++ b/jme3-niftygui/build.gradle @@ -1,6 +1,6 @@ dependencies { api project(':jme3-core') - api "com.github.nifty-gui:nifty:${niftyVersion}" - api "com.github.nifty-gui:nifty-default-controls:${niftyVersion}" - runtimeOnly "com.github.nifty-gui:nifty-style-black:${niftyVersion}" + api libs.nifty + api libs.nifty.default.controls + runtimeOnly libs.nifty.style.black } diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.j3md b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.j3md index 73c71ac47d..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 GLSL150: Common/MatDefs/Nifty/NiftyQuad.vert - FragmentShader GLSL100 GLSL150: 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/NiftyQuadGrad.j3md b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.j3md index ca351d8ea7..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 GLSL150: Common/MatDefs/Nifty/NiftyQuadGrad.vert - FragmentShader GLSL100 GLSL150: 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/NiftyTex.j3md b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.j3md index e1d26494b8..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 GLSL150: Common/MatDefs/Nifty/NiftyTex.vert - FragmentShader GLSL100 GLSL150: 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-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-vr/src/main/java/test/TestInitHmd.java b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonPrimitive.java similarity index 69% rename from jme3-vr/src/main/java/test/TestInitHmd.java rename to jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonPrimitive.java index a15ea732d1..91399bcebb 100644 --- a/jme3-vr/src/main/java/test/TestInitHmd.java +++ b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonPrimitive.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 @@ -29,31 +29,36 @@ * 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; +package com.jme3.plugins.gson; -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.openvr.OpenVR; -import com.jme3.system.AppSettings; +import com.jme3.plugins.json.JsonPrimitive; /** - * Testing OpenVR environment. - * @author Rickard (neph1 @ github) + * GSON implementation of {@link JsonPrimitive} */ -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(); +public class GsonPrimitive extends GsonElement implements JsonPrimitive { - openVr.destroy(); - + public GsonPrimitive(com.google.gson.JsonPrimitive element) { + super(element); } -} \ No newline at end of file + + 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-vr/src/main/java/com/jme3/input/vr/oculus/package-info.java b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/package-info.java similarity index 92% rename from jme3-vr/src/main/java/com/jme3/input/vr/oculus/package-info.java rename to jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/package-info.java index ab7b1bbb89..df1e88063f 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/package-info.java +++ b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,9 @@ * 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 for Oculus Rift headsets + * A JSON parser that uses Gson internally */ -package com.jme3.input.vr.oculus; + +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-vr/src/main/java/com/jme3/input/vr/package-info.java b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/package-info.java similarity index 91% rename from jme3-vr/src/main/java/com/jme3/input/vr/package-info.java rename to jme3-plugins-json/src/main/java/com/jme3/plugins/json/package-info.java index b260858f81..55a9208e1c 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/package-info.java +++ b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,8 @@ * 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 for Virtual Reality (VR) applications + * An abstraction layer to implement JSON parsers in jMonkeyEngine */ -package com.jme3.input.vr; +package com.jme3.plugins.json; diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index b88f0a64fd..e84f234a68 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -11,6 +11,8 @@ sourceSets { dependencies { api project(':jme3-core') - api 'com.google.code.gson:gson:2.9.1' + + implementation project(':jme3-plugins-json') + implementation project(':jme3-plugins-json-gson') testRuntimeOnly project(':jme3-desktop') } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java index 450b79f4fc..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 @@ -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 @@ -72,11 +73,41 @@ public void add(String name, String layerName, int firstFrame, int lastFrame) { 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/SceneKey.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java index 4b82490af3..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,6 +32,7 @@ package com.jme3.scene.plugins.fbx; import com.jme3.asset.ModelKey; +import java.util.Objects; public class SceneKey extends ModelKey { @@ -50,5 +51,28 @@ public SceneKey(String name, AnimationList 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/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java index 5e76201819..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,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,13 +31,14 @@ */ 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; @@ -45,19 +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 final Map defaultExtensionLoaders = new HashMap<>(); + + static final Map> defaultExtensionLoaders = new ConcurrentHashMap<>(); + static { + 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() { - defaultExtensionLoaders.put("KHR_materials_pbrSpecularGlossiness", new PBRSpecGlossExtensionLoader()); - defaultExtensionLoaders.put("KHR_lights_punctual", new LightsPunctualExtensionLoader()); - defaultExtensionLoaders.put("KHR_materials_unlit", new UnlitExtensionLoader()); - defaultExtensionLoaders.put("KHR_texture_transform", new TextureTransformExtensionLoader()); + } + + /** + * 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) { @@ -106,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()); @@ -130,14 +183,20 @@ private T readExtension(String name, JsonElement el, T input) throws AssetLo @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(); } - ExtrasLoader loader; - loader = key.getExtrasLoader(); - if (loader == null) { + + if (loader == null) { // if no loader was found, use the default extras loader + loader = getDefaultExtrasLoader(); + } + + 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 4bdbb29eff..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,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,8 +31,7 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonElement; - +import com.jme3.plugins.json.JsonElement; import java.io.IOException; /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java index c470478db1..1267cddf05 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java @@ -31,7 +31,7 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonElement; +import com.jme3.plugins.json.JsonElement; /** * Interface to handle a glTF extra. diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GlbLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GlbLoader.java index 85a1caa5ca..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,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,10 +32,14 @@ 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. @@ -43,14 +47,25 @@ public class GlbLoader extends GltfLoader { private static final int JSON_TYPE = 0x4E4F534A; - 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())); + LittleEndien stream = new LittleEndien(new BufferedInputStream(assetInfo.openStream())); /* magic */ stream.readInt(); - /* version */ 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; @@ -63,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 2475c01ea9..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,5 +1,5 @@ /* - * Copyright (c) 2009-2023 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,8 +31,10 @@ */ 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; @@ -46,11 +48,14 @@ 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; @@ -107,7 +112,6 @@ public Object load(AssetInfo assetInfo) throws IOException { protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws IOException { try { - dataCache.clear(); info = assetInfo; skinnedSpatials.clear(); rootNode = new Node(); @@ -119,7 +123,7 @@ protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws defaultMat.setFloat("Roughness", 1f); } - docRoot = JsonParser.parseReader(new JsonReader(new InputStreamReader(stream))).getAsJsonObject(); + docRoot = parse(stream); JsonObject asset = docRoot.getAsJsonObject().get("asset").getAsJsonObject(); getAsString(asset, "generator"); @@ -163,7 +167,7 @@ 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) { Node child = (Node) rootNode.getChild(0); // Migrate lights that were in the parent to the child. @@ -179,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; } } @@ -194,7 +219,7 @@ 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"); } @@ -229,7 +254,7 @@ 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, @@ -248,18 +273,16 @@ public Object readNode(int nodeIndex) throws IOException { // 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. @@ -330,12 +353,12 @@ public Transform readTransforms(JsonObject nodeData) { 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( @@ -364,138 +387,145 @@ 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(); - 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); - - 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 enabled for 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); + geomArray = customContentManager.readExtensionAndExtras("mesh", meshData, geomArray); - addToCache("meshes", meshIndex, geomArray, meshes.size()); - return geomArray; + addToCache("meshes", meshIndex, geomArray, meshes.size()); + } + // 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) { @@ -546,11 +576,15 @@ public Object readBuffer(Integer bufferViewIndex, int byteOffset, int count, Obj // 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); + 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) { @@ -562,14 +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; } @@ -581,12 +641,12 @@ public byte[] readData(int bufferIndex) throws IOException { 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)); + data = BufferUtils.createByteBuffer(Base64.getDecoder().decode(uri.substring(uri.indexOf(",") + 1))); } else { // external file let's load it String decoded = decodeUri(uri); @@ -596,14 +656,14 @@ protected byte[] getBytes(int bufferIndex, String uri, Integer bufferLength) thr } BinDataKey key = new BinDataKey(info.getKey().getFolder() + decoded); - InputStream input = (InputStream) info.getManager().loadAsset(key); - data = new byte[bufferLength]; - DataInputStream dataStream = new DataInputStream(input); - dataStream.readFully(data); - dataStream.close(); + 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; @@ -740,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); @@ -754,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; } @@ -770,19 +837,23 @@ 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 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 String decoded = decodeUri(uri); @@ -800,7 +871,7 @@ 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 TrackData[] tracks = new TrackData[nodes.size()]; boolean hasMorphTrack = false; @@ -900,17 +971,16 @@ public void readAnimation(int animationIndex) throws IOException { 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; @@ -936,9 +1006,9 @@ public void readAnimation(int animationIndex) throws IOException { } } - // Check each bone to see if their local pose is different from their bind pose. + // 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 JME way of applying anim transforms will apply the bind pose to those bones, + // 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); @@ -955,7 +1025,7 @@ public void readAnimation(int animationIndex) throws IOException { } } } - + anim.setTracks(aTracks.toArray(new AnimTrack[aTracks.size()])); anim = customContentManager.readExtensionAndExtras("animations", animation, anim); @@ -968,7 +1038,7 @@ public void readAnimation(int animationIndex) throws IOException { if (!spatials.isEmpty()) { if (skinIndex != -1) { // there are some spatial or morph tracks in this bone animation... or the other way around. - // Let's add the spatials in the skinnedSpatials. + // Let's add the spatials to the skinnedSpatials. SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class); List spat = skinnedSpatials.get(skin); spat.addAll(spatials); @@ -1032,7 +1102,7 @@ public void readSkins() throws IOException { 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 + // 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. @@ -1050,7 +1120,7 @@ public void readSkins() throws IOException { // 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. + // since the skeleton is not guaranteed to be exported in bind pose. Integer matricesIndex = getAsInteger(skin, "inverseBindMatrices"); Matrix4f[] inverseBindMatrices = null; if (matricesIndex != null) { @@ -1194,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); @@ -1319,13 +1395,14 @@ public VertexBuffer populate(Integer bufferViewIndex, int componentType, String } int numComponents = getNumberOfComponents(type); - Buffer buff = VertexBuffer.createBuffer(format, numComponents, count); int bufferSize = numComponents * count; + Buffer buff; if (bufferViewIndex == null) { + 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) { @@ -1347,7 +1424,7 @@ public float[] populate(Integer bufferViewIndex, int componentType, String type, 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, @@ -1369,7 +1446,7 @@ public float[] populate(Integer bufferViewIndex, int componentType, String type, // 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, @@ -1391,7 +1468,7 @@ public Vector3f[] populate(Integer bufferViewIndex, int componentType, String ty 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, @@ -1411,7 +1488,7 @@ public Quaternion[] populate(Integer bufferViewIndex, int componentType, String 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, @@ -1460,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); @@ -1469,4 +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 34f43dde50..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,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,6 +35,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * An optional key to use when loading a glTF file @@ -53,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); @@ -114,4 +115,33 @@ public ExtrasLoader getExtrasLoader() { 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 e5b42d99a1..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,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,15 +31,22 @@ */ 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; @@ -57,6 +64,20 @@ public class GltfUtils { 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; @@ -213,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 populateBuffer(Object store, byte[] source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + 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, 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; @@ -277,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; @@ -312,69 +342,69 @@ 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 @@ -385,63 +415,82 @@ public static float readAsFloat(LittleEndien stream, VertexBuffer.Format format) int c; switch (format) { case Byte: - c = stream.readByte(); + c = source.get(); return Math.max(c / 127f, -1f); case UnsignedByte: - c = stream.readUnsignedByte(); + c = source.get() & 0xFF; return c / 255f; case Short: - c = stream.readShort(); + c = source.getShort(); return Math.max(c / 32767f, -1f); - case UnsignedShort: - c = stream.readUnsignedShort(); + 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; } @@ -525,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; @@ -722,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); } @@ -869,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 index 6b98e3f8d7..112c6f3700 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,9 +31,9 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; import com.jme3.asset.AssetLoadException; import com.jme3.light.DirectionalLight; import com.jme3.light.Light; @@ -209,6 +209,7 @@ private void addLight(Node parent, Node node, int 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); 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 cb2639dc8f..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 @@ -62,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; @@ -75,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 993d0411a7..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 @@ -34,10 +34,17 @@ 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"); @@ -54,21 +61,29 @@ 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) { if (param.getName().equals("alpha")) { String alphaMode = (String) param.getValue(); switch (alphaMode) { - case "MASK": // fallthrough + case "MASK": + // "MASK" -> BlendMode.Off + getMaterial().setFloat("AlphaDiscardThreshold", MASK_ALPHA_DISCARD); + break; case "BLEND": getMaterial().getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); break; } - // Alpha is a RenderState not a Material Parameter, so return null + + // 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. @@ -76,14 +91,14 @@ protected MatParam adaptMatParam(MatParam param) { } // 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/PBRSpecGlossExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java index 365c49eb29..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,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,11 +31,10 @@ */ 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/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index d54382d8ac..aff6fbdcf1 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,9 +31,9 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; import com.jme3.asset.AssetLoadException; import com.jme3.math.Matrix3f; import com.jme3.math.Vector3f; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java index b9e8d5066a..790d70b0cf 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,7 +31,7 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonElement; +import com.jme3.plugins.json.JsonElement; import com.jme3.asset.AssetKey; /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java index 53e247bb0c..65acdf7f2b 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,6 +32,7 @@ package com.jme3.scene.plugins.gltf; import com.jme3.material.MatParam; +import com.jme3.material.Material; import com.jme3.material.RenderState; /** @@ -77,5 +78,9 @@ protected MatParam adaptMatParam(MatParam param) { 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/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 4bb9282270..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 @@ -55,7 +55,12 @@ public void save(Savable object, OutputStream f) throws IOException { } @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(); + } + 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/J3MRootOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java index 74af239d69..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,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.material.plugin.export.material; import com.jme3.export.OutputCapsule; @@ -8,48 +39,81 @@ 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(Writer out) throws IOException { - out.write("Material " + name + " : " + materialDefinition + " {\n\n"); + 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); } @@ -60,7 +124,7 @@ public void writeToStream(Writer 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; @@ -72,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"); } @@ -86,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/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 e5dfa75965..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-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,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; @@ -58,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(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/xml/java/com/jme3/export/xml/DOMInputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java index 52cd509f10..1d91ca903b 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java @@ -55,11 +55,10 @@ * @author blaine */ public class DOMInputCapsule implements InputCapsule { - private static final Logger logger = - Logger.getLogger(DOMInputCapsule.class .getName()); + private static final Logger logger = Logger.getLogger(DOMInputCapsule.class .getName()); private Document doc; - private Element currentElem; + private Element currentElement; private XMLImporter importer; private boolean isAtRoot = true; private Map referencedSavables = new HashMap<>(); @@ -70,1369 +69,862 @@ public class DOMInputCapsule implements InputCapsule { public DOMInputCapsule(Document doc, XMLImporter importer) { this.doc = doc; this.importer = importer; - currentElem = doc.getDocumentElement(); + currentElement = doc.getDocumentElement(); - 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) { + + private Element findChildElement(String name) { + if (currentElement == null) { return null; } - s = s.replaceAll("\\"", "\"").replaceAll("\\<", "<").replaceAll("\\&", "&"); - return s; - } - - private Element findFirstChildElement(Element parent) { - Node ret = parent.getFirstChild(); - while (ret != null && (!(ret instanceof Element))) { + Node ret = currentElement.getFirstChild(); + while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) { ret = ret.getNextSibling(); } return (Element) ret; } - private Element findChildElement(Element parent, String name) { - if (parent == null) { + // helper method to reduce duplicate code. checks that number of tokens in the "data" attribute matches the "size" attribute + // and returns an array of parsed primitives. + private Object readPrimitiveArrayHelper(Element element, String primType) throws IOException { + if (element == null) { return null; } - Node ret = parent.getFirstChild(); - while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) { - ret = ret.getNextSibling(); + + String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_SIZE); + + if (sizeString.isEmpty()) { + return null; } - return (Element) ret; - } - private Element findNextSiblingElement(Element current) { - Node ret = current.getNextSibling(); - while (ret != null) { - if (ret instanceof Element) { - return (Element) ret; + String[] tokens = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_DATA)); + + if(!sizeString.isEmpty()) { + try { + int requiredSize = Integer.parseInt(sizeString); + if (tokens.length != requiredSize) { + throw new IOException("Wrong token count for '" + element.getTagName() + + "'. size says " + requiredSize + + ", data contains " + + tokens.length); + } + } catch (NumberFormatException ex) { + throw new IOException("Invalid size for '" + element.getTagName() + "': " + sizeString); + } + } + + try { + switch (primType) { + case "byte": + byte[] byteArray = new byte[tokens.length]; + for (int i = 0; i < tokens.length; i++) byteArray[i] = Byte.parseByte(tokens[i]); + return byteArray; + case "short": + short[] shortArray = new short[tokens.length]; + for (int i = 0; i < tokens.length; i++) shortArray[i] = Short.parseShort(tokens[i]); + return shortArray; + case "int": + int[] intArray = new int[tokens.length]; + for (int i = 0; i < tokens.length; i++) intArray[i] = Integer.parseInt(tokens[i]); + return intArray; + case "long": + long[] longArray = new long[tokens.length]; + for (int i = 0; i < tokens.length; i++) longArray[i] = Long.parseLong(tokens[i]); + return longArray; + case "float": + float[] floatArray = new float[tokens.length]; + for (int i = 0; i < tokens.length; i++) floatArray[i] = Float.parseFloat(tokens[i]); + return floatArray; + case "double": + double[] doubleArray = new double[tokens.length]; + for (int i = 0; i < tokens.length; i++) doubleArray[i] = Double.parseDouble(tokens[i]); + return doubleArray; + case "boolean": + boolean[] booleanArray = new boolean[tokens.length]; + for (int i = 0; i < tokens.length; i++) booleanArray[i] = Boolean.parseBoolean(tokens[i]); + return booleanArray; + default: + throw new IOException(); // will never happen + } + } catch(NumberFormatException nfe) { + throw new IOException(nfe); + } + } + + // helper method to reduce duplicate code. checks that number of child elements matches the "size" attribute + // and returns a convenient list of child elements. + private List getObjectArrayElements(Element element) throws IOException { + if (element == null) { + return null; + } + + String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_SIZE); + + if (sizeString.isEmpty()) { + return null; + } + + NodeList nodes = element.getChildNodes(); + + List elements = new ArrayList<>(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node instanceof Element) { + elements.add((Element) node); } - ret = ret.getNextSibling(); } - return null; + + try { + int requiredSize = Integer.parseInt(sizeString); + if (elements.size() != requiredSize) { + throw new IOException("DOMInputCapsule.getObjectArrayElements(): Wrong element count for '" + element.getTagName() + + "'. size says " + requiredSize + + ", data contains " + + elements.size()); + } + } catch (NumberFormatException ex) { + throw new IOException("Invalid size for '" + element.getTagName() + "': " + sizeString); + } + + return elements; } @Override public byte readByte(String name, byte defVal) throws IOException { - String tmpString = 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 Byte.parseByte(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Byte.parseByte(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override public byte[] readByteArray(String name, byte[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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 bytes for '" + name - + "'. size says " + requiredSize - + ", data contains " - + strings.length); - } - byte[] tmp = new byte[strings.length]; - for (int i = 0; i < strings.length; i++) { - tmp[i] = Byte.parseByte(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + byte[] array = (byte[]) readPrimitiveArrayHelper(findChildElement(name), "byte"); + return array != null ? array : defVal; } @Override public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - String sizeString = tmpEl.getAttribute("size"); - NodeList nodes = currentElem.getChildNodes(); - List byteArrays = new ArrayList<>(); + if (arrayEntryElements == null) { + return defVal; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - byteArrays.add(readByteArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (byteArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + byteArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return byteArrays.toArray(new byte[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + byte[][] arrays = new byte[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (byte[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "byte"); } + + return arrays; } @Override - public int readInt(String name, int defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return Integer.parseInt(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + public short readShort(String name, short defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; } - } - @Override - public int[] readIntArray(String name, int[] defVal) throws IOException { try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Short.parseShort(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public int[][] readIntArray2D(String name, int[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - - + public short[] readShortArray(String name, short[] defVal) throws IOException { + short[] array = (short[]) readPrimitiveArrayHelper(findChildElement(name), "short"); + return array != null ? array : defVal; + } + @Override + public short[][] readShortArray2D(String name, short[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - NodeList nodes = currentElem.getChildNodes(); - List intArrays = new ArrayList<>(); + if (arrayEntryElements == null) { + return defVal; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - intArrays.add(readIntArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (intArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + intArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return intArrays.toArray(new int[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + short[][] arrays = new short[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (short[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "short"); } + + return arrays; } @Override - public float readFloat(String name, float defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public int readInt(String name, int defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Float.parseFloat(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Integer.parseInt(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public float[] readFloatArray(String name, float[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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; - } + public int[] readIntArray(String name, int[] defVal) throws IOException { + int[] array = (int[]) readPrimitiveArrayHelper(findChildElement(name), "int"); + return array != null ? array : defVal; } @Override - public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException { - /* Why does this one method ignore the 'size attr.? */ - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer")); - int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer")); + public int[][] readIntArray2D(String name, int[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - float[][] tmp = new float[size_outer][size_inner]; + if (arrayEntryElements == null) { + return defVal; + } - String[] strings = parseTokens(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 | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + int[][] arrays = new int[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (int[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "int"); } + + return arrays; } @Override - public double readDouble(String name, double defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public long readLong(String name, long defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Double.parseDouble(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Long.parseLong(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public double[] readDoubleArray(String name, double[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + public long[] readLongArray(String name, long[] defVal) throws IOException { + long[] array = (long[]) readPrimitiveArrayHelper(findChildElement(name), "long"); + return array != null ? array : defVal; } @Override - public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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 | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + public long[][] readLongArray2D(String name, long[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + long[][] arrays = new long[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (long[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "long"); } + + return arrays; } @Override - public long readLong(String name, long defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public float readFloat(String name, float defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Long.parseLong(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Float.parseFloat(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public long[] readLongArray(String name, long[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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 | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + public float[] readFloatArray(String name, float[] defVal) throws IOException { + float[] array = (float[]) readPrimitiveArrayHelper(findChildElement(name), "float"); + return array != null ? array : defVal; } @Override - public long[][] readLongArray2D(String name, long[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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 | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + float[][] arrays = new float[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (float[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "float"); } + + return arrays; } @Override - public short readShort(String name, short defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public double readDouble(String name, double defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Short.parseShort(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Double.parseDouble(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public short[] readShortArray(String name, short[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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 | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + public double[] readDoubleArray(String name, double[] defVal) throws IOException { + double[] array = (double[]) readPrimitiveArrayHelper(findChildElement(name), "double"); + return array != null ? array : defVal; } @Override - public short[][] readShortArray2D(String name, short[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } + public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - String sizeString = tmpEl.getAttribute("size"); - NodeList nodes = currentElem.getChildNodes(); - List shortArrays = new ArrayList<>(); + if (arrayEntryElements == null) { + return defVal; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - shortArrays.add(readShortArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (shortArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + shortArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return shortArrays.toArray(new short[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + double[][] arrays = new double[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (double[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "double"); } + + return arrays; } @Override public boolean readBoolean(String name, boolean defVal) throws IOException { - String tmpString = 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 | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + boolean[][] arrays = new boolean[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (boolean[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "boolean"); } + + return arrays; } @Override public String readString(String name, String defVal) throws IOException { - String tmpString = 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 | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + List arrayElements = getObjectArrayElements(findChildElement(name)); + + if (arrayElements == null) { + return defVal; } + + Element oldElement = currentElement; + + String[] array = new String[arrayElements.size()]; + for (int i = 0; i < arrayElements.size(); i++) { + currentElement = arrayElements.get(i); + array[i] = readString("value", null); + } + + currentElement = oldElement; + + return array; } @Override public String[][] readStringArray2D(String name, String[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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 | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + Element outerArrayElement = findChildElement(name); + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + if (innerArrayElements == null) { + return defVal; } + + currentElement = outerArrayElement; + + String[][] arrays = new String[innerArrayElements.size()][]; + for (int i = 0; i < innerArrayElements.size(); i++) { + arrays[i] = readStringArray(innerArrayElements.get(i).getTagName(), null); + } + + currentElement = (Element) currentElement.getParentNode(); + + return arrays; } @Override - public BitSet readBitSet(String name, BitSet defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public > T readEnum(String name, Class enumType, T defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - BitSet set = new BitSet(); - String[] strings = parseTokens(tmpString); - for (int i = 0; i < strings.length; i++) { - int isSet = Integer.parseInt(strings[i]); - if (isSet == 1) { - set.set(i); - } - } - return set; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Enum.valueOf(enumType, attribute); + } catch (IllegalArgumentException | NullPointerException e) { + throw new IOException(e); } } @Override - public Savable readSavable(String name, Savable defVal) throws IOException { - Savable ret = defVal; - if (name != null && name.equals("")) - logger.warning("Reading Savable String with name \"\"?"); - try { - Element tmpEl = null; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - } else if (isAtRoot) { - tmpEl = doc.getDocumentElement(); - isAtRoot = false; - } else { - tmpEl = findFirstChildElement(currentElem); - } - currentElem = tmpEl; - ret = readSavableFromCurrentElem(defVal); - if (currentElem.getParentNode() instanceof Element) { - currentElem = (Element) currentElem.getParentNode(); - } else { - currentElem = null; + public BitSet readBitSet(String name, BitSet defVal) throws IOException { + String attribute = null; + + attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute == null || attribute.isEmpty()) { + return defVal; + } + + String[] strings = parseTokens(attribute); + BitSet bitSet = new BitSet(); + for (int i = 0; i < strings.length; i++) { + if (strings[i].equals("1")) { + bitSet.set(i); } - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } - return ret; - } - private Savable readSavableFromCurrentElem(Savable defVal) throws - InstantiationException, ClassNotFoundException, - NoSuchMethodException, InvocationTargetException, - IOException, IllegalAccessException { - Savable ret = defVal; - Savable tmp = null; + return bitSet; + } - if (currentElem == null || currentElem.getNodeName().equals("null")) { - return null; + private Savable readSavableFromCurrentElement(Savable defVal) throws IOException { + if (currentElement == null || !currentElement.hasAttributes()) { + return defVal; } - String reference = 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; + } + + List arrayElements = getObjectArrayElements(arrayElement); + + Savable[] savableArray = new Savable[arrayElements.size()]; + + Element old = currentElement; + + for (int i = 0; i < arrayElements.size(); i++) { + currentElement = arrayElements.get(i); + savableArray[i] = readSavableFromCurrentElement(null); } + + currentElement = old; + + return savableArray; } @Override public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException { - Savable[][] ret = defVal; - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + Element outerArrayElement = findChildElement(name); - int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer")); - int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer")); + if (outerArrayElement == null || !outerArrayElement.hasAttributes()) { + return defVal; + } - Savable[][] tmp = new Savable[size_outer][size_inner]; - currentElem = findFirstChildElement(tmpEl); - for (int i = 0; i < size_outer; i++) { - for (int j = 0; j < size_inner; j++) { - tmp[i][j] = (readSavableFromCurrentElem(null)); - if (i == size_outer - 1 && j == size_inner - 1) { - break; - } - currentElem = findNextSiblingElement(currentElem); - } + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + Savable[][] savableArray2D = new Savable[innerArrayElements.size()][]; + + Element old = currentElement; + + for (int i = 0; i < innerArrayElements.size(); i++) { + List savableElements = getObjectArrayElements(innerArrayElements.get(i)); + + if (savableElements == null) { + continue; + } + + savableArray2D[i] = new Savable[savableElements.size()]; + for (int j = 0; j < savableElements.size(); j++) { + currentElement = savableElements.get(j); + savableArray2D[i][j] = readSavableFromCurrentElement(null); } - ret = tmp; - currentElem = (Element) tmpEl.getParentNode(); - return ret; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } + + currentElement = old; + + return savableArray2D; } @Override - @SuppressWarnings("unchecked") - public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException { + Element element = findChildElement(name); - String sizeString = tmpEl.getAttribute("size"); - ArrayList savables = new ArrayList<>(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - savables.add(readSavableFromCurrentElem(null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (savables.size() != requiredSize) - throw new IOException( - "Wrong number of Savable arrays for '" + name - + "'. size says " + requiredSize - + ", data contains " + savables.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return savables; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + byte[] array = (byte[]) readPrimitiveArrayHelper(element, "byte"); + + if (array == null) { + return defVal; } + + return (ByteBuffer) BufferUtils.createByteBuffer(array.length).put(array).rewind(); } @Override - @SuppressWarnings("unchecked") - public ArrayList[] readSavableArrayListArray( - String name, ArrayList[] defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - currentElem = tmpEl; - - String sizeString = 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); - } + public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException { + Element element = findChildElement(name); + + short[] array = (short[]) readPrimitiveArrayHelper(element, "short"); - if (requiredSize > -1 && savableArrayLists.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + savableArrayLists.size()); - currentElem = (Element) tmpEl.getParentNode(); - return savableArrayLists.toArray(new ArrayList[0]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + if (array == null) { + return defVal; } + + return (ShortBuffer) BufferUtils.createShortBuffer(array.length).put(array).rewind(); } @Override - @SuppressWarnings("unchecked") - public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - currentElem = tmpEl; - String sizeString = 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; + public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException { + Element element = findChildElement(name); + + int[] array = (int[]) readPrimitiveArrayHelper(element, "int"); + + if (array == null) { + return defVal; } + + return (IntBuffer) BufferUtils.createIntBuffer(array.length).put(array).rewind(); } @Override - public ArrayList readFloatBufferArrayList( - String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException { + Element element = findChildElement(name); - String sizeString = 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 | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + float[] array = (float[]) readPrimitiveArrayHelper(element, "float"); + + if (array == null) { + return defVal; } + + return (FloatBuffer) BufferUtils.createFloatBuffer(array.length).put(array).rewind(); } @Override - public Map readSavableMap(String name, Map defVal) throws IOException { - Map ret; - Element tempEl; + public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException { + byte[][] byteArray2D = readByteArray2D(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; + if (byteArray2D == null) { + return defVal; } - ret = new HashMap(); - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - Savable key = readSavable(XMLExporter.ELEMENT_KEY, null); - Savable val = readSavable(XMLExporter.ELEMENT_VALUE, null); - ret.put(key, val); - } + ArrayList byteBufferList = new ArrayList<>(byteArray2D.length); + for (byte[] byteArray : byteArray2D) { + if (byteArray == null) { + byteBufferList.add(null); + } else { + byteBufferList.add((ByteBuffer) BufferUtils.createByteBuffer(byteArray.length).put(byteArray).rewind()); + } } - currentElem = (Element) tempEl.getParentNode(); - return ret; + + return byteBufferList; } @Override - public Map readStringSavableMap(String name, Map defVal) throws IOException { - Map ret = null; - Element tempEl; + public ArrayList readFloatBufferArrayList(String name, ArrayList defVal) throws IOException { + float[][] floatArray2D = readFloatArray2D(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; - } - if (tempEl != null) { - ret = new HashMap(); - - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - String key = currentElem.getAttribute("key"); - Savable val = readSavable("Savable", null); - ret.put(key, val); - } - } - } else { - return defVal; + if (floatArray2D == null) { + return defVal; + } + + ArrayList floatBufferList = new ArrayList<>(floatArray2D.length); + for (float[] floatArray : floatArray2D) { + if (floatArray == null) { + floatBufferList.add(null); + } else { + floatBufferList.add((FloatBuffer) BufferUtils.createFloatBuffer(floatArray.length).put(floatArray).rewind()); } - currentElem = (Element) tempEl.getParentNode(); - return ret; + } + + return floatBufferList; } @Override - public IntMap readIntSavableMap(String name, IntMap defVal) throws IOException { - IntMap ret = null; - Element tempEl; + @SuppressWarnings("unchecked") + public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException { + Savable[] savableArray = readSavableArray(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; - } - if (tempEl != null) { - ret = new IntMap(); - - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - int key = Integer.parseInt(currentElem.getAttribute("key")); - Savable val = readSavable("Savable", null); - ret.put(key, val); - } - } - } else { - return defVal; - } - currentElem = (Element) tempEl.getParentNode(); - return ret; + if (savableArray == null) { + return defVal; + } + + return new ArrayList(Arrays.asList(savableArray)); } - /** - * reads from currentElem if name is null - */ @Override - public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = 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); + @SuppressWarnings("unchecked") + public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException { + Savable[][] savableArray2D = readSavableArray2D(name, null); + + if (savableArray2D == null) { + return defVal; + } + + ArrayList[] savableArrayListArray = new ArrayList[savableArray2D.length]; + for (int i = 0; i < savableArray2D.length; i++) { + if (savableArray2D[i] != null) { + savableArrayListArray[i] = new ArrayList(Arrays.asList(savableArray2D[i])); } - FloatBuffer tmp = BufferUtils.createFloatBuffer(strings.length); - for (String s : strings) tmp.put(Float.parseFloat(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + return savableArrayListArray; } @Override - public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + @SuppressWarnings("unchecked") + public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException { + Element outerArrayElement = findChildElement(name); - String sizeString = 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); + if (outerArrayElement == null) { + return defVal; + } + + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + ArrayList[][] savableArrayListArray2D = new ArrayList[innerArrayElements.size()][]; + + Element old = currentElement; + currentElement = outerArrayElement; + + for (int i = 0; i < innerArrayElements.size(); i++) { + if (innerArrayElements.get(i) != null) { + + savableArrayListArray2D[i] = readSavableArrayListArray(innerArrayElements.get(i).getTagName(), null); } - IntBuffer tmp = BufferUtils.createIntBuffer(strings.length); - for (String s : strings) tmp.put(Integer.parseInt(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + currentElement = old; + + return savableArrayListArray2D; } @Override - public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public Map readSavableMap(String name, Map defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = 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 (mapElement == null) { + return defVal; + } + + Map ret = new HashMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + Savable key = readSavable(XMLExporter.ELEMENT_KEY, null); + Savable val = readSavable(XMLExporter.ELEMENT_VALUE, null); + ret.put(key, val); } - ByteBuffer tmp = BufferUtils.createByteBuffer(strings.length); - for (String s : strings) tmp.put(Byte.valueOf(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + + return ret; } @Override - public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public Map readStringSavableMap(String name, Map defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = 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 (mapElement == null) { + return defVal; + } + + Map ret = new HashMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + String key = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "key"); + Savable val = readSavable("Savable", null); + ret.put(key, val); } - ShortBuffer tmp = BufferUtils.createShortBuffer(strings.length); - for (String s : strings) tmp.put(Short.valueOf(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + + return ret; } @Override - public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public IntMap readIntSavableMap(String name, IntMap defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = 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()); - } - currentElem = (Element) tmpEl.getParentNode(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + if (mapElement == null) { + return defVal; } - @Override - public > T readEnum(String name, Class enumType, - T defVal) throws IOException { - T ret = defVal; - try { - String eVal = currentElem.getAttribute(name); - if (eVal != null && eVal.length() > 0) { - ret = Enum.valueOf(enumType, eVal); + IntMap ret = new IntMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + int key = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "key")); + Savable val = readSavable("Savable", null); + ret.put(key, val); } - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + return ret; - } + } private static final String[] zeroStrings = new String[0]; 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 182d4103cd..58d1713de9 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java @@ -39,13 +39,13 @@ import com.jme3.export.SavableClassUtil; import com.jme3.util.IntMap; import com.jme3.util.IntMap.Entry; +import java.lang.reflect.Array; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.*; -import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -56,8 +56,6 @@ * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5 */ public class DOMOutputCapsule implements OutputCapsule { - - private static final String dataAttributeName = "data"; private Document doc; private Element currentElement; private JmeExporter exporter; @@ -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 f0e664427b..af5f898bf8 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java @@ -46,7 +46,9 @@ * @author Brett McLaughlin, Justin Edelson - Original creation for "Java and XML" book. * @author Doug Daniels (dougnukem) - adjustments for XML formatting * @version $Revision: 4207 $, $Date: 2009-03-29 11:19:16 -0400 (Sun, 29 Mar 2009) $ + * @deprecated This class was only used in XMLExporter and has been replaced by javax.xml.transform.Transformer */ +@Deprecated public class DOMSerializer { /** The encoding to use for output (default is UTF-8) */ diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java index 5472737bc6..bc4ef918b0 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java @@ -39,8 +39,15 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import org.w3c.dom.Document; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; /** * Part of the jME XML IO system as introduced in the Google Code jmexml project. @@ -53,31 +60,63 @@ public class XMLExporter implements JmeExporter { public static final String ELEMENT_MAPENTRY = "MapEntry"; public static final String ELEMENT_KEY = "Key"; public static final String ELEMENT_VALUE = "Value"; - public static final String ELEMENT_FLOATBUFFER = "FloatBuffer"; public static final String ATTRIBUTE_SIZE = "size"; + public static final String ATTRIBUTE_DATA = "data"; + public static final String ATTRIBUTE_CLASS = "class"; + public static final String ATTRIBUTE_REFERENCE_ID = "reference_ID"; + public static final String ATTRIBUTE_REFERENCE = "ref"; + public static final String ATTRIBUTE_SAVABLE_VERSIONS = "savable_versions"; private DOMOutputCapsule domOut; + + private int indentSpaces = 4; public XMLExporter() { } @Override - public void save(Savable object, OutputStream f) throws IOException { + public void save(Savable object, OutputStream outputStream) throws IOException { + Document document = null; try { - // Initialize the Document when saving, so we don't retain state of previous exports. - this.domOut = new DOMOutputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(), this); - domOut.write(object, object.getClass().getName(), null); - DOMSerializer serializer = new DOMSerializer(); - serializer.serialize(domOut.getDoc(), f); - f.flush(); + document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); } catch (ParserConfigurationException ex) { throw new IOException(ex); } + document.setXmlStandalone(true); // for some reason the transformer output property below doesn't work unless the document is set to standalone + + // Initialize the DOMOutputCapsule when saving, so we don't retain state of previous exports. + domOut = new DOMOutputCapsule(document, this); + + domOut.write(object, "savable", null); + + DOMSource source = new DOMSource(domOut.getDoc()); + StreamResult result = new StreamResult(outputStream); + + try { + TransformerFactory tfFactory = TransformerFactory.newInstance(); + tfFactory.setAttribute("indent-number", indentSpaces); + + Transformer transformer = tfFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); + + if (indentSpaces > 0) { + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + } + + transformer.transform(source, result); + } catch (TransformerException ex) { + throw new IOException(ex); + } } @Override - 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 7cf2a98cb2..90d9e6e057 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java @@ -80,8 +80,9 @@ public Object load(AssetInfo info) throws IOException { try { return load(in); } finally { - if (in != null) + if (in != null) { in.close(); + } } } @@ -115,5 +116,4 @@ public InputCapsule getCapsule(Savable id) { public static XMLImporter getInstance() { return new XMLImporter(); } - } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java 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-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-vr/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBufferVR.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotTestBase.java similarity index 68% rename from jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBufferVR.java rename to jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotTestBase.java index 234d36d8de..81553e614e 100644 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBufferVR.java +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotTestBase.java @@ -1,50 +1,56 @@ -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); - } -} +/* + * 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/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java index 8331bfd578..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 @@ -56,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; @@ -677,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); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java index 6401939315..f96d13c384 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -321,8 +321,7 @@ public boolean load() { logger.fine("Created heightmap using Particle Deposition"); - - return false; + return true; // success } /** diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag index 14899a0eca..9cad93f886 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag @@ -1,643 +1,164 @@ #extension GL_EXT_texture_array : enable #import "Common/ShaderLib/GLSLCompat.glsllib" -#import "Common/ShaderLib/PBR.glsllib" -#import "Common/ShaderLib/Lighting.glsllib" -#import "Common/MatDefs/Terrain/AfflictionLib.glsllib" -varying vec3 wPosition; -varying vec3 vNormal; -varying vec2 texCoord; -uniform vec3 g_CameraPosition; -varying vec3 vPosition; -varying vec3 vnPosition; -varying vec3 vViewDir; -varying vec4 vLightDir; -varying vec4 vnLightDir; -varying vec3 lightVec; -varying vec3 inNormal; -varying vec3 wNormal; - -#ifdef DEBUG_VALUES_MODE - uniform int m_DebugValuesMode; -#endif +#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 -uniform vec4 g_LightData[NB_LIGHTS]; -uniform vec4 g_AmbientLightColor; +#define ENABLE_PBRTerrainUtils_readPBRTerrainLayers 1 -#if NB_PROBES >= 1 - uniform samplerCube g_PrefEnvMap; - uniform vec3 g_ShCoeffs[9]; - uniform mat4 g_LightProbeData; +#import "Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib" +#import "Common/MatDefs/Terrain/Modular/PBRTerrainUtils.glsllib" +#ifdef AFFLICTIONTEXTURE + #import "Common/MatDefs/Terrain/Modular/AfflictionLib.glsllib" #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 TRI_PLANAR_MAPPING - varying vec4 wVertex; -#endif - -//texture arrays: -uniform sampler2DArray m_AlbedoTextureArray; -uniform sampler2DArray m_NormalParallaxTextureArray; -uniform sampler2DArray m_MetallicRoughnessAoEiTextureArray; -//texture-slot params for 12 unique texture slots (0-11) where the integer value points to the desired texture's index in the corresponding texture array: -#for i=0..12 ( $0 ) - uniform int m_AfflictionMode_$i; - uniform float m_Roughness_$i; - uniform float m_Metallic_$i; - uniform float m_AlbedoMap_$i_scale; - uniform vec4 m_EmissiveColor_$i; - - #ifdef ALBEDOMAP_$i - uniform int m_AlbedoMap_$i; - #endif - #ifdef NORMALMAP_$i - uniform int m_NormalMap_$i; - #endif - #ifdef METALLICROUGHNESSMAP_$i - uniform int m_MetallicRoughnessMap_$i; - #endif -#endfor - -//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 +//declare PBR Lighting vars +uniform vec4 g_LightData[NB_LIGHTS]; +uniform vec3 g_CameraPosition; -#ifdef DISCARD_ALPHA - uniform float m_AlphaDiscardThreshold; +#ifdef DEBUG_VALUES_MODE + uniform int m_DebugValuesMode; #endif -//fog vars for basic fog : #ifdef USE_FOG -#import "Common/ShaderLib/MaterialFog.glsllib" - uniform vec4 m_FogColor; - float fogDistance; - - uniform vec2 m_LinearFog; + #import "Common/ShaderLib/MaterialFog.glsllib" #endif -#ifdef FOG_EXP - uniform float m_ExpFog; -#endif -#ifdef FOG_EXPSQ - uniform float m_ExpSqFog; -#endif - -//sun intensity is a secondary AO value that can be painted per-vertex in the red channel of the -// vertex colors, or it can be set as a static value for an entire material with the StaticSunIntensity float param -#if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) - varying vec4 vertColors; -#endif - -#ifdef STATIC_SUN_INTENSITY - uniform float m_StaticSunIntensity; -#endif -//sun intensity AO value is only applied to the directional light, not to point lights, so it is important to track if the -//sun is more/less bright than the brightest point light for each fragment to determine how the light probe's ambient light should be scaled later on in light calculation code -float brightestPointLight = 0.0; -//optional affliction paramaters that use the AfflictionAlphaMap's green channel for splatting m_SplatAlbedoMap and the red channel for splatting desaturation : -#ifdef AFFLICTIONTEXTURE - uniform sampler2D m_AfflictionAlphaMap; -#endif -#ifdef USE_SPLAT_NOISE - uniform float m_SplatNoiseVar; -#endif -//only defined for non-terrain geoemtries and terrains that are not positioned nor sized in correlation to the 2d array of AfflictionAlphaMaps used for splatting accross large tile based scenes in a grid -#ifdef TILELOCATION - uniform float m_TileWidth; - uniform vec3 m_TileLocation; -#endif -#ifdef AFFLICTIONALBEDOMAP - uniform sampler2D m_SplatAlbedoMap; -#endif -#ifdef AFFLICTIONNORMALMAP - uniform sampler2D m_SplatNormalMap; -#endif -#ifdef AFFLICTIONROUGHNESSMETALLICMAP - uniform sampler2D m_SplatRoughnessMetallicMap; -#endif -#ifdef AFFLICTIONEMISSIVEMAP - uniform sampler2D m_SplatEmissiveMap; -#endif - -uniform int m_AfflictionSplatScale; -uniform float m_AfflictionRoughnessValue; -uniform float m_AfflictionMetallicValue; -uniform float m_AfflictionEmissiveValue; -uniform vec4 m_AfflictionEmissiveColor; - -vec4 afflictionVector; -float noiseHash; -float livelinessValue; -float afflictionValue; -int afflictionMode = 1; - -//general temp vars : -vec4 tempAlbedo, tempNormal, tempEmissiveColor; -float tempParallax, tempMetallic, tempRoughness, tempAo, tempEmissiveIntensity; - -vec3 viewDir; -vec2 coord; -vec4 albedo = vec4(1.0); -vec3 normal = vec3(0.5,0.5,1); -vec3 norm; -float Metallic; -float Roughness; -float packedAoValue = 1.0; -vec4 emissive; -float emissiveIntensity = 1.0; -float indoorSunLightExposure = 1.0; - -vec4 packedMetallicRoughnessAoEiVec; -vec4 packedNormalParallaxVec; - - -void main(){ - - #ifdef USE_FOG - fogDistance = distance(g_CameraPosition, wPosition.xyz); - #endif +void main(){ + vec3 wpos = PBRLightingUtils_getWorldPosition(); + vec3 worldViewDir = normalize(g_CameraPosition - wpos); - float indoorSunLightExposure = 1.0; + // Create a blank PBRSurface. + PBRSurface surface = PBRLightingUtils_createPBRSurface(worldViewDir); - viewDir = normalize(g_CameraPosition - wPosition); - - norm = normalize(wNormal); - normal = norm; - + //pre-calculate necessary values for tri-planar blending + TriPlanarUtils_calculateBlending(surface.geometryNormal); - afflictionVector = vec4(1.0, 0.0, 1.0, 0.0); //r channel is sturation, g channel is affliction splat texture intensity, b and a unused (might use b channel for wetness eventually) - + //reads terrain alphaMaps + PBRTerrainUtils_readAlphaMaps(); + + //CUSTOM LIB EXAMPLE: #ifdef AFFLICTIONTEXTURE - - #ifdef TILELOCATION - //subterrains that are not centred in tile or equal to tile width in total size need to have m_TileWidth pre-set. (tileWidth is the x,z 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); + AfflictionLib_readAfflictionVector(); + #endif - afflictionVector = texture2D(m_AfflictionAlphaMap, tileCoords).rgba; + // 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 - - #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 - #endif - - livelinessValue = afflictionVector.r; - afflictionValue = afflictionVector.g; - - - #ifdef ALBEDOMAP_0 - #ifdef ALPHAMAP - - vec4 alphaBlend; - vec4 alphaBlend_0, alphaBlend_1, alphaBlend_2; - int texChannelForAlphaBlending; - - alphaBlend_0 = texture2D( m_AlphaMap, texCoord.xy ); - - #ifdef ALPHAMAP_1 - alphaBlend_1 = texture2D( m_AlphaMap_1, texCoord.xy ); - #endif - #ifdef ALPHAMAP_2 - alphaBlend_2 = texture2D( m_AlphaMap_2, texCoord.xy ); - #endif - - vec2 texSlotCoords; - - float finalAlphaBlendForLayer = 1.0; - - vec3 blending = abs( norm ); - blending = (blending -0.2) * 0.7; - blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) - float b = (blending.x + blending.y + blending.z); - blending /= vec3(b, b, b); - - #for i=0..12 (#ifdef ALBEDOMAP_$i $0 #endif) - - //assign texture slot's blending from index's correct alpha map - if($i <= 3){ - alphaBlend = alphaBlend_0; - }else if($i <= 7){ - alphaBlend = alphaBlend_1; - }else if($i <= 11){ - alphaBlend = alphaBlend_2; - } - - texChannelForAlphaBlending = int(mod($i, 4.0)); //pick the correct channel (r g b or a) based on the layer's index - switch(texChannelForAlphaBlending) { - case 0: - finalAlphaBlendForLayer = alphaBlend.r; - break; - case 1: - finalAlphaBlendForLayer = alphaBlend.g; - break; - case 2: - finalAlphaBlendForLayer = alphaBlend.b; - break; - case 3: - finalAlphaBlendForLayer = alphaBlend.a; - break; - } - - afflictionMode = m_AfflictionMode_$i; - tempEmissiveColor = m_EmissiveColor_$i; - - #ifdef TRI_PLANAR_MAPPING - //tri planar - tempAlbedo = getTriPlanarBlendFromTexArray(wVertex, blending, m_AlbedoMap_$i, m_AlbedoMap_$i_scale, m_AlbedoTextureArray); - - #ifdef NORMALMAP_$i - packedNormalParallaxVec.rgba = getTriPlanarBlendFromTexArray(wVertex, blending, m_NormalMap_$i, m_AlbedoMap_$i_scale, m_NormalParallaxTextureArray).rgba; - tempNormal.xyz = calculateTangentsAndApplyToNormals(packedNormalParallaxVec.xyz, wNormal);// this gets rid of the need for pre-generating tangents for TerrainPatches, since doing so doesn't seem to work (tbnMat is always blank for terrains even with tangents pre-generated, not sure why...) - tempParallax = packedNormalParallaxVec.w; - - #ifdef PARALLAXHEIGHT_0 - //wip - #endif - #else - tempNormal.rgb = wNormal.rgb; - #endif - #ifdef METALLICROUGHNESSMAP_$i - packedMetallicRoughnessAoEiVec = getTriPlanarBlendFromTexArray(wVertex, blending, m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, m_MetallicRoughnessAoEiTextureArray).rgba; - tempRoughness = packedMetallicRoughnessAoEiVec.g * m_Roughness_$i; - tempMetallic = packedMetallicRoughnessAoEiVec.b * m_Metallic_$i; - tempAo = packedMetallicRoughnessAoEiVec.r; - tempEmissiveIntensity = packedMetallicRoughnessAoEiVec.a; - #endif - #else - - // non triplanar - texSlotCoords = texCoord * m_AlbedoMap_$i_scale; - - tempAlbedo = texture2DArray(m_AlbedoTextureArray, vec3(texSlotCoords, m_AlbedoMap_$i)); - - #ifdef NORMALMAP_$i - packedNormalParallaxVec = texture2DArray(m_NormalParallaxTextureArray, vec3(texSlotCoords, m_NormalMap_$i)); - tempNormal.xyz = calculateTangentsAndApplyToNormals(packedNormalParallaxVec.xyz, wNormal); - tempParallax = packedNormalParallaxVec.w; - - #ifdef PARALLAXHEIGHT_0 - //eventually add parallax code here if a PARALLAXHEIGHT_$i float is defined. but this shader is at the define limit currently, - // so to do that will require removing defines around scale to use that for enabling parallax per layer instead, since there's no reason for define around basic float scale anyways - #endif - #else - tempNormal.rgb = wNormal.rgb; - #endif - - #ifdef METALLICROUGHNESSMAP_$i - packedMetallicRoughnessAoEiVec = texture2DArray(m_MetallicRoughnessAoEiTextureArray, vec3(texSlotCoords, m_MetallicRoughnessMap_$i)); - tempRoughness = packedMetallicRoughnessAoEiVec.g * m_Roughness_$i; - tempMetallic = packedMetallicRoughnessAoEiVec.b * m_Metallic_$i; - tempAo = packedMetallicRoughnessAoEiVec.r; - tempEmissiveIntensity = packedMetallicRoughnessAoEiVec.a; - #endif - #endif - - - //blend to float values if no texture value for mrao map exists - #if !defined(METALLICROUGHNESSMAP_$i) - tempRoughness = m_Roughness_$i; - tempMetallic = m_Metallic_$i; - tempAo = 1.0; + 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 - - //note: most of these functions can be found in AfflictionLib.glslib - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode); //changes saturation of albedo for this layer; does nothing if not using AfflictionAlphaMap for affliction splatting - - tempEmissiveColor *= tempEmissiveIntensity; - - //mix values from this index layer to final output values based on finalAlphaBlendForLayer - albedo.rgb = mix(albedo.rgb, tempAlbedo.rgb , finalAlphaBlendForLayer); - normal.rgb = mix(normal.rgb, tempNormal.rgb, finalAlphaBlendForLayer); - Metallic = mix(Metallic, tempMetallic, finalAlphaBlendForLayer); - Roughness = mix(Roughness, tempRoughness, finalAlphaBlendForLayer); - packedAoValue = mix(packedAoValue, tempAo, finalAlphaBlendForLayer); - emissiveIntensity = mix(emissiveIntensity, tempEmissiveIntensity, finalAlphaBlendForLayer); - emissive = mix(emissive, tempEmissiveColor, finalAlphaBlendForLayer); - - #endfor + #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 - albedo = texture2D(m_AlbedoMap_0, texCoord); + #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 - #endif - float alpha = albedo.a; + //blends this layer + PBRTerrainUtils_blendPBRTerrainLayer(surface, terrainTextureLayer_$i); + #endfor + #ifdef DISCARD_ALPHA - if(alpha < m_AlphaDiscardThreshold){ + if(surface.alpha < m_AlphaDiscardThreshold){ discard; } - #endif - - //APPLY AFFLICTIONN TO THE PIXEL - #ifdef AFFLICTIONTEXTURE - vec4 afflictionAlbedo; - + #endif - float newAfflictionScale = m_AfflictionSplatScale; - vec2 newScaledCoords; + PBRLightingUtils_readSunLightExposureParams(surface); - - #ifdef AFFLICTIONALBEDOMAP - #ifdef TRI_PLANAR_MAPPING - newAfflictionScale = newAfflictionScale / 256; - afflictionAlbedo = getTriPlanarBlend(wVertex, blending, m_SplatAlbedoMap , newAfflictionScale); - #else - newScaledCoords = mod(wPosition.xz / m_AfflictionSplatScale, 0.985); - afflictionAlbedo = texture2D(m_SplatAlbedoMap , newScaledCoords); - #endif - - #else - afflictionAlbedo = vec4(1.0, 1.0, 1.0, 1.0); - #endif - - vec3 afflictionNormal; - #ifdef AFFLICTIONNORMALMAP - #ifdef TRI_PLANAR_MAPPING - - afflictionNormal = getTriPlanarBlend(wVertex, blending, m_SplatNormalMap , newAfflictionScale).rgb; - - #else - afflictionNormal = texture2D(m_SplatNormalMap , newScaledCoords).rgb; - #endif - - #else - afflictionNormal = norm; - - #endif - float afflictionMetallic = m_AfflictionMetallicValue; - float afflictionRoughness = m_AfflictionRoughnessValue; - float afflictionAo = 1.0; - - - vec4 afflictionEmissive = m_AfflictionEmissiveColor; - float afflictionEmissiveIntensity = m_AfflictionEmissiveValue; - - #ifdef AFFLICTIONROUGHNESSMETALLICMAP - vec4 metallicRoughnessAoEiVec; - #ifdef TRI_PLANAR_MAPPING - metallicRoughnessAoEiVec = texture2D(m_SplatRoughnessMetallicMap, newScaledCoords); - #else - metallicRoughnessAoEiVec = getTriPlanarBlend(wVertex, blending, m_SplatRoughnessMetallicMap, newAfflictionScale); - #endif - - 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(wVertex, blending, 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 - - Roughness = alterAfflictionRoughness(adjustedAfflictionValue, Roughness, afflictionRoughness, noiseHash); - Metallic = alterAfflictionMetallic(adjustedAfflictionValue, Metallic, afflictionMetallic, noiseHash); - albedo = alterAfflictionColor(adjustedAfflictionValue, albedo, afflictionAlbedo, noiseHash ); - normal = alterAfflictionNormalsForTerrain(adjustedAfflictionValue, normal, afflictionNormal, noiseHash , wNormal); - emissive = alterAfflictionGlow(adjustedAfflictionValue, emissive, afflictionEmissive, noiseHash); - emissiveIntensity = alterAfflictionEmissiveIntensity(adjustedAfflictionValue, emissiveIntensity, afflictionEmissiveIntensity, noiseHash); - emissiveIntensity *= afflictionEmissive.a; - //affliction ao value blended below after specular calculation - - #endif - - // spec gloss pipeline code would go here if supported, but likely will not be for terrain shaders as defines are limited and heavily used - - float specular = 0.5; - float nonMetalSpec = 0.08 * specular; - vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic; - vec4 diffuseColor = albedo - albedo * Metallic; - vec3 fZero = vec3(specular); - - gl_FragColor.rgb = vec3(0.0); - -//simple ao calculation, no support for lightmaps like stock pbr shader.. (probably could add lightmap support with another texture array, but -// that would add another texture read per slot and require removing 12 other defines to make room...) - vec3 ao = vec3(packedAoValue); - + //CUSTOM LIB EXAMPLE: uses a custom alpha map and noise to blend an extra splat layer overtop of all other layers #ifdef AFFLICTIONTEXTURE - ao = alterAfflictionAo(afflictionValue, ao, vec3(afflictionAo), noiseHash); // alter the AO map for affliction values + AfflictionLib_blendSplatLayers(surface); #endif - ao.rgb = ao.rrr; - specularColor.rgb *= ao; - - - - #ifdef STATIC_SUN_INTENSITY - indoorSunLightExposure = m_StaticSunIntensity; //single float value to indicate percentage of - //sunlight hitting the model (only works for small models or models with 100% consistent sunlighting accross every pixel) - #endif - #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY - indoorSunLightExposure = vertColors.r * indoorSunLightExposure; //use R channel of vertexColors for.. - #endif - // similar purpose as above... - //but uses r channel vert colors like an AO map specifically - //for sunlight (solution for scaling lighting for indoor - // and shadey/dimly lit models, especially big ones with) - brightestPointLight = 0.0; - - - 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); - - vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular; - - #if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) || defined(STATIC_SUN_INTENSITY) - if(fallOff == 1.0){ - directLighting.rgb *= indoorSunLightExposure;// ... *^. to scale down how intense just the sun is (ambient and direct light are 1.0 fallOff) - - } - else{ - brightestPointLight = max(fallOff, brightestPointLight); - - } - - #endif - - gl_FragColor.rgb += directLighting * fallOff; - } - - float minVertLighting; - #ifdef BRIGHTEN_INDOOR_SHADOWS - minVertLighting = 0.0833; //brighten shadows so that caves which are naturally covered from the DL shadows are not way too dark compared to when shadows are off (mostly only necessary for naturally dark scenes, or dark areas when using the sun intensity code above) - #else - minVertLighting = 0.0533; - - #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); - indoorSunLightExposure = max(indoorSunLightExposure, brightestPointLight); - indoorSunLightExposure = max(indoorSunLightExposure, minVertLighting); //scale the indoorSunLightExposure back up to account for the brightest point light nearby before scaling light probes by this value below - - #if NB_PROBES >= 1 - vec3 color1 = vec3(0.0); - vec3 color2 = vec3(0.0); - 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); - - 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 - -// multiply probes by the indoorSunLightExposure, as determined by pixel's sunlightExposure and adjusted for -// nearby point/spot lights ( will be multiplied by 1.0 and left unchanged if you are not defining any of the sunlight exposure variables for dimming indoors areas) - color1.rgb *= indoorSunLightExposure; - color2.rgb *= indoorSunLightExposure; - color3.rgb *= indoorSunLightExposure; - - - gl_FragColor.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0); - - #endif + // 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; - if(emissive.a > 0){ - emissive = emissive * pow(emissive.a * 5, emissiveIntensity) * emissiveIntensity * 20 * emissive.a; - } - // emissive = emissive * pow(emissiveIntensity * 2.3, emissive.a); - - gl_FragColor += emissive; - - // add fog after the lighting because shadows will cause the fog to darken - // which just results in the geometry looking like it's changed color #ifdef USE_FOG - #ifdef FOG_LINEAR - gl_FragColor = getFogLinear(gl_FragColor, m_FogColor, m_LinearFog.x, m_LinearFog.y, fogDistance); - #endif - #ifdef FOG_EXP - gl_FragColor = getFogExp(gl_FragColor, m_FogColor, m_ExpFog, fogDistance); - #endif - #ifdef FOG_EXPSQ - gl_FragColor = getFogExpSquare(gl_FragColor, m_FogColor, m_ExpSqFog, fogDistance); - #endif - #endif - - //outputs the final value of the selected layer as a color for debug purposes. - #ifdef DEBUG_VALUES_MODE - if(m_DebugValuesMode == 0){ - gl_FragColor.rgb = vec3(albedo); - } - else if(m_DebugValuesMode == 1){ - gl_FragColor.rgb = vec3(normal); - } - else if(m_DebugValuesMode == 2){ - gl_FragColor.rgb = vec3(Roughness); - } - else if(m_DebugValuesMode == 3){ - gl_FragColor.rgb = vec3(Metallic); - } - else if(m_DebugValuesMode == 4){ - gl_FragColor.rgb = ao.rgb; - } - else if(m_DebugValuesMode == 5){ - gl_FragColor.rgb = vec3(emissive.rgb); - } + gl_FragColor = MaterialFog_calculateFogColor(vec4(gl_FragColor)); #endif - gl_FragColor.a = albedo.a; - + //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 index 192c175165..2098bd77f3 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md @@ -1,28 +1,36 @@ -// NOTE: Doesn't support OpenGL1 -MaterialDef Advanced PBR Terrain { +MaterialDef AdvancedPBRTerrain { MaterialParameters { + Int BoundDrawBuffer - - Boolean UseVertexColorsAsSunIntensity //set true to make the vertex color's R channel how exposed a vertex is to the sun - Float StaticSunIntensity //used for setting the sun exposure value for a whole material + 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 @@ -37,7 +45,6 @@ MaterialDef Advanced PBR Terrain { Float SplatNoiseVar - Int AfflictionMode_0 : 1 Int AfflictionMode_1 : 1 Int AfflictionMode_2 : 1 @@ -103,7 +110,6 @@ MaterialDef Advanced PBR Terrain { Int AlbedoMap_10 Int AlbedoMap_11 - Float AlbedoMap_0_scale : 1 Float AlbedoMap_1_scale : 1 Float AlbedoMap_2_scale : 1 @@ -117,6 +123,18 @@ MaterialDef Advanced PBR Terrain { 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 @@ -131,7 +149,6 @@ MaterialDef Advanced PBR Terrain { Int NormalMap_10 Int NormalMap_11 - Int MetallicRoughnessMap_0 Int MetallicRoughnessMap_1 Int MetallicRoughnessMap_2 @@ -145,7 +162,6 @@ MaterialDef Advanced PBR Terrain { Int MetallicRoughnessMap_10 Int MetallicRoughnessMap_11 - Float ParallaxHeight_0 Float ParallaxHeight_1 Float ParallaxHeight_2 @@ -159,19 +175,22 @@ MaterialDef Advanced PBR Terrain { 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 + // debug the final value of the selected layer as a color output Int DebugValuesMode - // Layers: - // 0 - albedo (un-shaded) + // 0 - albedo (unshaded) // 1 - normals // 2 - roughness // 3 - metallic // 4 - ao - // 5 - emissive - + // 5 - emissive + // 6 - exposure + // 7 - alpha + // 8 - geometryNormals // use tri-planar mapping Boolean useTriPlanarMapping @@ -181,13 +200,6 @@ MaterialDef Advanced PBR Terrain { Texture2D AlphaMap_1 -LINEAR Texture2D AlphaMap_2 -LINEAR - Boolean UseSpecGloss - Texture2D SpecularMap - Texture2D GlossinessMap - Texture2D SpecularGlossinessMap - Color Specular : 1.0 1.0 1.0 1.0 - Float Glossiness : 1.0 - Vector4 ProbeData // Prefiltered Env Map for indirect specular lighting @@ -199,7 +211,6 @@ MaterialDef Advanced PBR Terrain { //integrate BRDF map for indirect Lighting Texture2D IntegrateBRDF -LINEAR - //shadows Int FilterMode Boolean HardwareShadows @@ -255,8 +266,8 @@ MaterialDef Advanced PBR Terrain { LightMode SinglePassAndImageBased - VertexShader GLSL150: Common/MatDefs/Terrain/PBRTerrain.vert - FragmentShader GLSL150: Common/MatDefs/Terrain/AdvancedPBRTerrain.frag + VertexShader GLSL300 GLSL150 : Common/MatDefs/Terrain/PBRTerrain.vert + FragmentShader GLSL300 GLSL150 : Common/MatDefs/Terrain/AdvancedPBRTerrain.frag WorldParameters { WorldViewProjectionMatrix @@ -266,10 +277,33 @@ MaterialDef Advanced PBR Terrain { 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 @@ -278,19 +312,14 @@ MaterialDef Advanced PBR Terrain { AFFLICTIONEMISSIVEMAP : SplatEmissiveMap USE_SPLAT_NOISE : SplatNoiseVar - TRI_PLANAR_MAPPING : useTriPlanarMapping + USE_TRIPLANAR_AFFLICTION_MAPPING : UseTriplanarAfflictionMapping - DISCARD_ALPHA : AlphaDiscardThreshold + TRI_PLANAR_MAPPING : useTriPlanarMapping ALPHAMAP : AlphaMap ALPHAMAP_1 : AlphaMap_1 ALPHAMAP_2 : AlphaMap_2 - USE_FOG : UseFog - FOG_LINEAR : LinearFog - FOG_EXP : ExpFog - FOG_EXPSQ : ExpSqFog - ALBEDOMAP_0 : AlbedoMap_0 ALBEDOMAP_1 : AlbedoMap_1 ALBEDOMAP_2 : AlbedoMap_2 @@ -330,18 +359,29 @@ MaterialDef Advanced PBR Terrain { METALLICROUGHNESSMAP_10 : MetallicRoughnessMap_10 METALLICROUGHNESSMAP_11 : MetallicRoughnessMap_11 - DEBUG_VALUES_MODE : DebugValuesMode - + 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 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 @@ -351,6 +391,7 @@ MaterialDef Advanced PBR Terrain { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones INSTANCING : UseInstancing @@ -368,8 +409,8 @@ MaterialDef Advanced PBR Terrain { Technique PostShadow{ - VertexShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -379,6 +420,7 @@ MaterialDef Advanced PBR Terrain { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge @@ -398,7 +440,4 @@ MaterialDef Advanced PBR Terrain { PolyOffset -0.1 0 } } - - - } diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AfflictionLib.glsllib b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AfflictionLib.glsllib deleted file mode 100644 index 2c96614adb..0000000000 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AfflictionLib.glsllib +++ /dev/null @@ -1,343 +0,0 @@ -#import "Common/MatDefs/Terrain/NoiseLib.glsllib" - -//code for tri-planar mapping on any afflicted shaders -vec4 getTriPlanarBlend(in vec4 coords, in vec3 blending, 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 * blending.x + col2 * blending.y + col3 * blending.z; - - return tex; -} - -vec4 getTriPlanarBlendFromTexArray(in vec4 coords, in vec3 blending, in int idInTexArray, in float scale, in sampler2DArray texArray) { - vec4 col1 = texture2DArray( texArray, vec3((coords.yz * scale), idInTexArray ) ); - vec4 col2 = texture2DArray( texArray, vec3((coords.xz * scale), idInTexArray ) ); - vec4 col3 = texture2DArray( texArray, vec3((coords.xy * scale), idInTexArray ) ); - // blend the results of the 3 planar projections. - vec4 tex = col1 * blending.x + col2 * blending.y + col3 * blending.z; - - return tex; -} - - -//used for mixing normal map normals with the world normals. texture slots without a normal map use wNormal as their blending value instead -vec3 calculateTangentsAndApplyToNormals(in vec3 normalIn, in vec3 worldNorm){ - - - vec3 returnNorm = normalize((normalIn.xyz * vec3(2.0) - vec3(1.0))); - - - vec3 baseNorm = worldNorm.rgb + vec3(0, 0, 1); - returnNorm *= vec3(-1, -1, 1); - returnNorm = baseNorm.rgb*dot(baseNorm.rgb, returnNorm.rgb)/baseNorm.z - returnNorm.rgb; - - returnNorm = normalize(returnNorm); - - - return returnNorm; -} - - -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; -} - -//methods for terrains - 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){ - //change hue - - 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; -} - - - -//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 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); - } - - - 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; -} - - - - - - - - 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 e8ec3589b3..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 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) - 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 GLSL150: Common/MatDefs/Terrain/HeightBasedTerrain.vert - FragmentShader GLSL100 GLSL150: 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/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/NoiseLib.glsllib b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/NoiseLib.glsllib deleted file mode 100644 index 2fc6444872..0000000000 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/NoiseLib.glsllib +++ /dev/null @@ -1,107 +0,0 @@ -//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 affliction 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; -} \ No newline at end of file diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag deleted file mode 100644 index f4dfa78cd1..0000000000 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag +++ /dev/null @@ -1,575 +0,0 @@ -#import "Common/ShaderLib/GLSLCompat.glsllib" -#import "Common/ShaderLib/PBR.glsllib" -#import "Common/ShaderLib/Parallax.glsllib" -#import "Common/ShaderLib/Lighting.glsllib" -#import "Common/MatDefs/Terrain/AfflictionLib.glsllib" - -varying vec3 wPosition; -varying vec3 vNormal; -varying vec2 texCoord; -uniform vec3 g_CameraPosition; -varying vec3 vPosition; -varying vec3 vnPosition; -varying vec3 vViewDir; -varying vec4 vLightDir; -varying vec4 vnLightDir; -varying vec3 lightVec; -varying vec3 inNormal; -varying vec3 wNormal; - -#ifdef DEBUG_VALUES_MODE - uniform int m_DebugValuesMode; -#endif - -uniform vec4 g_LightData[NB_LIGHTS]; -uniform vec4 g_AmbientLightColor; - -#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 TRI_PLANAR_MAPPING - varying vec4 wVertex; -#endif - -//texture-slot params for 12 unique texture slots (0-11) : -#for i=0..12 ( $0 ) - uniform int m_AfflictionMode_$i; - uniform float m_Roughness_$i; - uniform float m_Metallic_$i; - - #ifdef ALBEDOMAP_$i - uniform sampler2D m_AlbedoMap_$i; - #endif - #ifdef ALBEDOMAP_$i_SCALE - uniform float m_AlbedoMap_$i_scale; - #endif - #ifdef NORMALMAP_$i - uniform sampler2D m_NormalMap_$i; - #endif -#endfor - -//3 alpha maps : -#ifdef ALPHAMAP - uniform sampler2D m_AlphaMap; -#endif -#ifdef ALPHAMAP_1 - uniform sampler2D m_AlphaMap_1; -#endif -#ifdef ALPHAMAP_2 - uniform sampler2D m_AlphaMap_2; -#endif - -#ifdef DISCARD_ALPHA - uniform float m_AlphaDiscardThreshold; -#endif - -//fog vars for basic fog : -#ifdef USE_FOG -#import "Common/ShaderLib/MaterialFog.glsllib" - uniform vec4 m_FogColor; - float fogDistance; - - uniform vec2 m_LinearFog; -#endif -#ifdef FOG_EXP - uniform float m_ExpFog; -#endif -#ifdef FOG_EXPSQ - uniform float m_ExpSqFog; -#endif - -//sun intensity is a secondary AO value that can be painted per-vertex in the red channel of the -// vertex colors, or it can be set as a static value for an entire material with the StaticSunIntensity float param -#if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) - varying vec4 vertColors; -#endif - -#ifdef STATIC_SUN_INTENSITY - uniform float m_StaticSunIntensity; -#endif -//sun intensity AO value is only applied to the directional light, not to point lights, so it is important to track if the -//sun is more/less bright than the brightest point light for each fragment to determine how the light probe's ambient light should be scaled later on in light calculation code -float brightestPointLight = 0.0; - -//optional affliction paramaters that use the AfflictionAlphaMap's green channel for splatting m_SplatAlbedoMap and the red channel for splatting desaturation : -#ifdef AFFLICTIONTEXTURE - uniform sampler2D m_AfflictionAlphaMap; -#endif -#ifdef USE_SPLAT_NOISE - uniform float m_SplatNoiseVar; -#endif -//only defined for non-terrain geoemtries and terrains that are not positioned nor sized in correlation to the 2d array of AfflictionAlphaMaps used for splatting accross large tile based scenes in a grid -#ifdef TILELOCATION - uniform float m_TileWidth; - uniform vec3 m_TileLocation; -#endif -#ifdef AFFLICTIONALBEDOMAP - uniform sampler2D m_SplatAlbedoMap; -#endif -#ifdef AFFLICTIONNORMALMAP - uniform sampler2D m_SplatNormalMap; -#endif -#ifdef AFFLICTIONROUGHNESSMETALLICMAP - uniform sampler2D m_SplatRoughnessMetallicMap; -#endif -#ifdef AFFLICTIONEMISSIVEMAP - uniform sampler2D m_SplatEmissiveMap; -#endif - -uniform int m_AfflictionSplatScale; -uniform float m_AfflictionRoughnessValue; -uniform float m_AfflictionMetallicValue; -uniform float m_AfflictionEmissiveValue; -uniform vec4 m_AfflictionEmissiveColor; - -vec4 afflictionVector; -float noiseHash; -float livelinessValue; -float afflictionValue; -int afflictionMode = 1; - -//general temp vars : -vec4 tempAlbedo, tempNormal, tempEmissiveColor; -float tempParallax, tempMetallic, tempRoughness, tempAo, tempEmissiveIntensity; - -vec3 viewDir; -vec2 coord; -vec4 albedo = vec4(1.0); -vec3 normal = vec3(0.5,0.5,1); -vec3 norm; -float Metallic; -float Roughness; -float packedAoValue = 1.0; -vec4 emissive; -float emissiveIntensity = 1.0; -float indoorSunLightExposure = 1.0; - -vec4 packedMetallicRoughnessAoEiVec; -vec4 packedNormalParallaxVec; - -void main(){ - - #ifdef USE_FOG - fogDistance = distance(g_CameraPosition, wPosition.xyz); - #endif - - indoorSunLightExposure = 1.0; - - viewDir = normalize(g_CameraPosition - wPosition); - - norm = normalize(wNormal); - normal = norm; - - afflictionVector = vec4(1.0, 0.0, 1.0, 0.0); //r channel is sturation, g channel is affliction splat texture intensity, b and a unused (might use b channel for wetness eventually) - - #ifdef AFFLICTIONTEXTURE - - #ifdef TILELOCATION - //subterrains that are not centred in tile or equal to tile width in total size need to have m_TileWidth pre-set. (tileWidth is the x,z 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 and location matches tileLocation, the terrain's texCoords can be used for simple texel fetching of the AfflictionAlphaMap - afflictionVector = texture2D(m_AfflictionAlphaMap, texCoord.xy).rgba; - #endif - #endif - - livelinessValue = afflictionVector.r; - afflictionValue = afflictionVector.g; - - #ifdef ALBEDOMAP_0 - #ifdef ALPHAMAP - - vec4 alphaBlend; - vec4 alphaBlend_0, alphaBlend_1, alphaBlend_2; - int texChannelForAlphaBlending; - - alphaBlend_0 = texture2D( m_AlphaMap, texCoord.xy ); - - #ifdef ALPHAMAP_1 - alphaBlend_1 = texture2D( m_AlphaMap_1, texCoord.xy ); - #endif - #ifdef ALPHAMAP_2 - alphaBlend_2 = texture2D( m_AlphaMap_2, texCoord.xy ); - #endif - - vec2 texSlotCoords; - - float finalAlphaBlendForLayer = 1.0; - - vec3 blending = abs( norm ); - blending = (blending -0.2) * 0.7; - blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) - float b = (blending.x + blending.y + blending.z); - blending /= vec3(b, b, b); - - #for i=0..12 (#ifdef ALBEDOMAP_$i $0 #endif) - - //assign texture slot's blending from index's correct alpha map - if($i <= 3){ - alphaBlend = alphaBlend_0; - }else if($i <= 7){ - alphaBlend = alphaBlend_1; - }else if($i <= 11){ - alphaBlend = alphaBlend_2; - } - - texChannelForAlphaBlending = int(mod($i, 4.0)); //pick the correct channel (r g b or a) based on the layer's index - switch(texChannelForAlphaBlending) { - case 0: - finalAlphaBlendForLayer = alphaBlend.r; - break; - case 1: - finalAlphaBlendForLayer = alphaBlend.g; - break; - case 2: - finalAlphaBlendForLayer = alphaBlend.b; - break; - case 3: - finalAlphaBlendForLayer = alphaBlend.a; - break; - } - - afflictionMode = m_AfflictionMode_$i; - - #ifdef TRI_PLANAR_MAPPING - //tri planar - tempAlbedo = getTriPlanarBlend(wVertex, blending, m_AlbedoMap_$i, m_AlbedoMap_$i_scale); - - #ifdef NORMALMAP_$i - tempNormal.rgb = getTriPlanarBlend(wVertex, blending, m_NormalMap_$i, m_AlbedoMap_$i_scale).rgb; - tempNormal.rgb = calculateTangentsAndApplyToNormals(tempNormal.rgb, wNormal);// this gets rid of the need for pre-generating tangents for TerrainPatches, since doing so doesn't seem to work (tbnMat is always blank for terrains even with tangents pre-generated, not sure why...) - #else - tempNormal.rgb = wNormal.rgb; - #endif - #else - - // non triplanar - texSlotCoords = texCoord * m_AlbedoMap_$i_scale; - - tempAlbedo.rgb = texture2D(m_AlbedoMap_$i, texSlotCoords).rgb; - #ifdef NORMALMAP_$i - tempNormal.xyz = texture2D(m_NormalMap_$i, texSlotCoords).xyz; - tempNormal.rgb = calculateTangentsAndApplyToNormals(tempNormal.rgb, wNormal); - #else - tempNormal.rgb = wNormal.rgb; - #endif - #endif - - //note: most of these functions can be found in AfflictionLib.glslib - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode); //changes saturation of albedo for this layer; does nothing if not using AfflictionAlphaMap for affliction splatting - - //mix values from this index layer to final output values based on finalAlphaBlendForLayer - albedo.rgb = mix(albedo.rgb, tempAlbedo.rgb , finalAlphaBlendForLayer); - normal.rgb = mix(normal.rgb, tempNormal.rgb, finalAlphaBlendForLayer); - Metallic = mix(Metallic, m_Metallic_$i, finalAlphaBlendForLayer); - Roughness = mix(Roughness, m_Roughness_$i, finalAlphaBlendForLayer); - - #endfor - #endif - #endif - - - float alpha = albedo.a; - #ifdef DISCARD_ALPHA - if(alpha < m_AlphaDiscardThreshold){ - discard; - } - #endif - - - //APPLY AFFLICTIONN TO THE PIXEL - #ifdef AFFLICTIONTEXTURE - vec4 afflictionAlbedo; - - float newAfflictionScale = m_AfflictionSplatScale; - vec2 newScaledCoords; - - #ifdef AFFLICTIONALBEDOMAP - #ifdef TRI_PLANAR_MAPPING - newAfflictionScale = newAfflictionScale / 256; - afflictionAlbedo = getTriPlanarBlend(wVertex, blending, m_SplatAlbedoMap , newAfflictionScale); - #else - newScaledCoords = mod(wPosition.xz / m_AfflictionSplatScale, 0.985); - afflictionAlbedo = texture2D(m_SplatAlbedoMap , newScaledCoords); - #endif - - #else - afflictionAlbedo = vec4(1.0, 1.0, 1.0, 1.0); - #endif - - vec3 afflictionNormal; - #ifdef AFFLICTIONNORMALMAP - #ifdef TRI_PLANAR_MAPPING - - afflictionNormal = getTriPlanarBlend(wVertex, blending, m_SplatNormalMap , newAfflictionScale).rgb; - - #else - afflictionNormal = texture2D(m_SplatNormalMap , newScaledCoords).rgb; - #endif - - #else - afflictionNormal = norm; - - #endif - float afflictionMetallic = m_AfflictionMetallicValue; - float afflictionRoughness = m_AfflictionRoughnessValue; - float afflictionAo = 1.0; - - - vec4 afflictionEmissive = m_AfflictionEmissiveColor; - float afflictionEmissiveIntensity = m_AfflictionEmissiveValue; - - - #ifdef AFFLICTIONROUGHNESSMETALLICMAP - vec4 metallicRoughnessAoEiVec = texture2D(m_SplatRoughnessMetallicMap, newScaledCoords); - afflictionRoughness *= metallicRoughnessAoEiVec.g; - afflictionMetallic *= metallicRoughnessAoEiVec.b; - afflictionAo = metallicRoughnessAoEiVec.r; - afflictionEmissiveIntensity *= metallicRoughnessAoEiVec.a; //important not to leave this channel all black by accident when creating the mraoei map if using affliction emissiveness - - #endif - - #ifdef AFFLICTIONEMISSIVEMAP - vec4 emissiveMapColor = texture2D(m_SplatEmissiveMap, newScaledCoords); - afflictionEmissive *= emissiveMapColor; - #endif - - float adjustedAfflictionValue = afflictionValue; - #ifdef USE_SPLAT_NOISE - noiseHash = getStaticNoiseVar0(wPosition, afflictionValue * m_SplatNoiseVar); - - adjustedAfflictionValue = getAdjustedAfflictionVar(afflictionValue); - if(afflictionValue >= 0.99){ - adjustedAfflictionValue = afflictionValue; - } - #else - noiseHash = 1.0; - #endif - - Roughness = alterAfflictionRoughness(adjustedAfflictionValue, Roughness, afflictionRoughness, noiseHash); - Metallic = alterAfflictionMetallic(adjustedAfflictionValue, Metallic, afflictionMetallic, noiseHash); - albedo = alterAfflictionColor(adjustedAfflictionValue, albedo, afflictionAlbedo, noiseHash ); - normal = alterAfflictionNormalsForTerrain(adjustedAfflictionValue, normal, afflictionNormal, noiseHash , wNormal); - emissive = alterAfflictionGlow(adjustedAfflictionValue, emissive, afflictionEmissive, noiseHash); - emissiveIntensity = alterAfflictionEmissiveIntensity(adjustedAfflictionValue, emissiveIntensity, afflictionEmissiveIntensity, noiseHash); - emissiveIntensity *= afflictionEmissive.a; - //affliction ao value blended below after specular calculation - #endif - -// spec gloss pipeline code would go here if supported, but likely will not be for terrain shaders as defines are limited and heavily used - -float specular = 0.5; -float nonMetalSpec = 0.08 * specular; -vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic; -vec4 diffuseColor = albedo - albedo * Metallic; -vec3 fZero = vec3(specular); - -gl_FragColor.rgb = vec3(0.0); - -//simple ao calculation, no support for lightmaps like stock pbr shader.. (probably could add lightmap support with another texture array, but -// that would add another texture read per slot and require removing 12 other defines to make room...) - vec3 ao = vec3(packedAoValue); - - #ifdef AFFLICTIONTEXTURE - ao = alterAfflictionAo(afflictionValue, ao, vec3(afflictionAo), noiseHash); // alter the AO map for affliction values - #endif - ao.rgb = ao.rrr; - specularColor.rgb *= ao; - - #ifdef STATIC_SUN_INTENSITY - indoorSunLightExposure = m_StaticSunIntensity; //single float value to indicate percentage of - //sunlight hitting the model (only works for small models or models with 100% consistent sunlighting accross every pixel) - #endif - #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY - indoorSunLightExposure = vertColors.r * indoorSunLightExposure; //use R channel of vertexColors for.. - #endif - // similar purpose as above... - //but uses r channel vert colors like an AO map specifically - //for sunlight (solution for scaling lighting for indoor - // and shadey/dimly lit models, especially big ones that - // span accross varying directionalLight exposure) - brightestPointLight = 0.0; - - - 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); - - vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular; - - #if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) || defined(STATIC_SUN_INTENSITY) - if(fallOff == 1.0){ - directLighting.rgb *= indoorSunLightExposure;// ... *^. to scale down how intense just the sun is (ambient and direct light are 1.0 fallOff) - - } - else{ - brightestPointLight = max(fallOff, brightestPointLight); - - } - #endif - - gl_FragColor.rgb += directLighting * fallOff; - - } - - float minVertLighting; - #ifdef BRIGHTEN_INDOOR_SHADOWS - minVertLighting = 0.0833; //brighten shadows so that caves/indoors which are naturally covered from the DL shadows are not way too dark compared to when shadows are off (mostly only necessary for naturally dark scenes, or dark areas when using the sun intensity code above) - #else - minVertLighting = 0.0533; - - #endif - - indoorSunLightExposure = max(indoorSunLightExposure, brightestPointLight); - indoorSunLightExposure = max(indoorSunLightExposure, minVertLighting); //scale the indoorSunLightExposure back up to account for the brightest point light nearby before scaling light probes by this value below - - #if NB_PROBES >= 1 - vec3 color1 = vec3(0.0); - vec3 color2 = vec3(0.0); - 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); - - 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 - - -// multiply probes by the indoorSunLightExposure, as determined by pixel's sunlightExposure and adjusted for -// nearby point/spot lights ( will be multiplied by 1.0 and left unchanged if you are not defining any of the sunlight exposure variables for dimming indoors areas) - color1.rgb *= indoorSunLightExposure; - color2.rgb *= indoorSunLightExposure; - color3.rgb *= indoorSunLightExposure; - - gl_FragColor.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0); - - #endif - - if(emissive.a > 0){ - emissive = emissive * pow(emissive.a * 5, emissiveIntensity) * emissiveIntensity * 20 * emissive.a; - } - - // emissive = emissive * pow(emissiveIntensity * 2.3, emissive.a); - - gl_FragColor += emissive; - - // add fog after the lighting because shadows will cause the fog to darken - // which just results in the geometry looking like it's changed color - #ifdef USE_FOG - #ifdef FOG_LINEAR - gl_FragColor = getFogLinear(gl_FragColor, m_FogColor, m_LinearFog.x, m_LinearFog.y, fogDistance); - #endif - #ifdef FOG_EXP - gl_FragColor = getFogExp(gl_FragColor, m_FogColor, m_ExpFog, fogDistance); - #endif - #ifdef FOG_EXPSQ - gl_FragColor = getFogExpSquare(gl_FragColor, m_FogColor, m_ExpSqFog, fogDistance); - #endif - #endif - - //outputs the final value of the selected layer as a color for debug purposes. - #ifdef DEBUG_VALUES_MODE - if(m_DebugValuesMode == 0){ - gl_FragColor.rgb = vec3(albedo); - } - else if(m_DebugValuesMode == 1){ - gl_FragColor.rgb = vec3(normal); - } - else if(m_DebugValuesMode == 2){ - gl_FragColor.rgb = vec3(Roughness); - } - else if(m_DebugValuesMode == 3){ - gl_FragColor.rgb = vec3(Metallic); - } - else if(m_DebugValuesMode == 4){ - gl_FragColor.rgb = ao.rgb; - } - else if(m_DebugValuesMode == 5){ - gl_FragColor.rgb = vec3(emissive.rgb); - } - #endif - gl_FragColor.a = albedo.a; - -} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md index 67796e5846..92e601c1c1 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md @@ -2,15 +2,18 @@ MaterialDef PBR Terrain { MaterialParameters { + Int BoundDrawBuffer - - Boolean UseVertexColorsAsSunIntensity //set true to make the vertex color's R channel how exposed a vertex is to the sun - Float StaticSunIntensity //used for setting the sun exposure value for a whole material + 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 @@ -23,6 +26,16 @@ MaterialDef PBR Terrain { 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 @@ -70,18 +83,44 @@ MaterialDef PBR Terrain { Float Metallic_10 : 0.0 Float Metallic_11 : 0.0 - - // debug the final value of the selected layer as a color output + 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 (un-shaded) + // 0 - albedo (unshaded) // 1 - normals // 2 - roughness // 3 - metallic // 4 - ao - // 5 - emissive - + // 5 - emissive + // 6 - exposure + // 7 - alpha + // 8 - geometryNormals // use tri-planar mapping Boolean useTriPlanarMapping @@ -146,21 +185,11 @@ MaterialDef PBR Terrain { Float AlbedoMap_11_scale Texture2D NormalMap_11 -LINEAR - - // Texture that specifies alpha values Texture2D AlphaMap -LINEAR Texture2D AlphaMap_1 -LINEAR Texture2D AlphaMap_2 -LINEAR - // For Spec gloss pipeline - Boolean UseSpecGloss - Texture2D SpecularMap - Texture2D GlossinessMap - Texture2D SpecularGlossinessMap - Color Specular : 1.0 1.0 1.0 1.0 - Float Glossiness : 1.0 - Vector4 ProbeData // Prefiltered Env Map for indirect specular lighting @@ -213,7 +242,6 @@ MaterialDef PBR Terrain { Boolean BackfaceShadows : false - Boolean UseFog Color FogColor Vector2 LinearFog @@ -228,8 +256,8 @@ MaterialDef PBR Terrain { LightMode SinglePassAndImageBased - VertexShader GLSL100 GLSL130 GLSL150: Common/MatDefs/Terrain/PBRTerrain.vert - FragmentShader GLSL100 GLSL130 GLSL150: Common/MatDefs/Terrain/PBRTerrain.frag + VertexShader GLSL300 GLSL150 GLSL130 GLSL100: Common/MatDefs/Terrain/PBRTerrain.vert + FragmentShader GLSL300 GLSL150 GLSL130 GLSL100: Common/MatDefs/Terrain/AdvancedPBRTerrain.frag WorldParameters { WorldViewProjectionMatrix @@ -239,14 +267,15 @@ MaterialDef PBR Terrain { ViewProjectionMatrix ViewMatrix Time - } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer TILELOCATION : TileLocation AFFLICTIONTEXTURE : AfflictionAlphaMap + USE_TRIPLANAR_AFFLICTION_MAPPING : UseTriplanarAfflictionMapping AFFLICTIONALBEDOMAP: SplatAlbedoMap AFFLICTIONNORMALMAP : SplatNormalMap AFFLICTIONROUGHNESSMETALLICMAP : SplatRoughnessMetallicMap @@ -254,11 +283,19 @@ MaterialDef PBR Terrain { USE_SPLAT_NOISE : SplatNoiseVar + SPECULAR_AA : UseSpecularAA + SPECULAR_AA_SCREEN_SPACE_VARIANCE : SpecularAASigma + SPECULAR_AA_THRESHOLD : SpecularAAKappa - USE_VERTEX_COLORS_AS_SUN_INTENSITY : UseVertexColorsAsSunIntensity - STATIC_SUN_INTENSITY : StaticSunIntensity + 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 @@ -310,6 +347,19 @@ MaterialDef PBR Terrain { 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 } @@ -318,8 +368,8 @@ MaterialDef PBR Terrain { 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 @@ -329,6 +379,7 @@ MaterialDef PBR Terrain { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones INSTANCING : UseInstancing @@ -346,8 +397,8 @@ MaterialDef PBR Terrain { Technique PostShadow{ - VertexShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -357,6 +408,7 @@ MaterialDef PBR Terrain { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.vert index 4ee749062d..8096ca9724 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.vert +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.vert @@ -8,16 +8,15 @@ attribute vec2 inTexCoord; varying vec2 texCoord; varying vec3 wPosition; varying vec3 wNormal; +varying vec3 lPosition; +uniform vec4 g_AmbientLightColor; - uniform vec4 g_AmbientLightColor; - - -#ifdef TRI_PLANAR_MAPPING - varying vec4 wVertex; +#ifdef USE_FOG + varying float fogDistance; + uniform vec3 g_CameraPosition; #endif - void main(){ vec4 modelSpacePos = vec4(inPosition, 1.0); @@ -28,13 +27,11 @@ void main(){ wPosition = (g_WorldMatrix * vec4(inPosition, 1.0)).xyz; wNormal = normalize(TransformWorldNormal(inNormal)); - - - #ifdef TRI_PLANAR_MAPPING - wVertex = vec4(inPosition,0.0); - #endif + lPosition = modelSpacePos.xyz; - + #ifdef USE_FOG + fogDistance = distance(g_CameraPosition, (g_WorldMatrix * modelSpacePos).xyz); + #endif -} \ No newline at end of file +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md index 98fe3eb9e7..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 GLSL150: Common/MatDefs/Terrain/Terrain.vert - FragmentShader GLSL100 GLSL150: 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/TerrainLighting.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag index f2ca013fc7..8125982303 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag @@ -3,6 +3,10 @@ #import "Common/ShaderLib/Lighting.glsllib" uniform float m_Shininess; +#ifdef SPECULARMAP + uniform sampler2D m_SpecularMap; +#endif + uniform vec4 g_LightDirection; varying vec4 AmbientSum; @@ -634,6 +638,19 @@ void main(){ vec3 normal = vNormal; #endif + //----------------------- + // read shininess or specularColor from specularMap (possibly want to create a new texture called ShininessMap if there is ever a need to have both a specularMap and reflectivityMap) + //----------------------- + vec4 specularColor = vec4(1.0); + float finalShininessValue = m_Shininess; + #ifdef SPECULARMAP + vec4 specularMapColor = texture2D(m_SpecularMap, texCoord); + #ifdef USE_SPECULARMAP_AS_SHININESS + finalShininessValue = specularMapColor.r; //assumes that specularMap is a gray-scale reflectivity/shininess map) + #else + specularColor = specularMapColor; + #endif + #endif //----------------------- // lighting calculations @@ -641,9 +658,7 @@ void main(){ vec4 lightDir = vLightDir; lightDir.xyz = normalize(lightDir.xyz); - vec2 light = computeLighting(normal, vViewDir.xyz, lightDir.xyz,lightDir.w*spotFallOff,m_Shininess); - - vec4 specularColor = vec4(1.0); + vec2 light = computeLighting(normal, vViewDir.xyz, lightDir.xyz,lightDir.w*spotFallOff,finalShininessValue); //-------------------------- // final color calculations diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md index 353ff4e627..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 GLSL150: Common/MatDefs/Terrain/TerrainLighting.vert - FragmentShader GLSL100 GLSL150: 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 GLSL150: Common/MatDefs/Terrain/SPTerrainLighting.vert - FragmentShader GLSL100 GLSL150: 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 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 @@ -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 GLSL150: Common/MatDefs/SSAO/normal.vert - FragmentShader GLSL100 GLSL150: 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 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 HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor } 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-vr/build.gradle b/jme3-vr/build.gradle deleted file mode 100644 index 6fbc20d209..0000000000 --- a/jme3-vr/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -dependencies { - api project(':jme3-core') - api project(':jme3-lwjgl3') - api project(':jme3-desktop') - api project(':jme3-effects') - - // https://mvnrepository.com/artifact/net.java.dev.jna/jna - implementation 'net.java.dev.jna:jna:5.10.0' - implementation 'com.nativelibs4java:jnaerator-runtime:0.12' - - // Native LibOVR/Oculus support - api "org.lwjgl:lwjgl-ovr:${lwjgl3Version}" - runtimeOnly "org.lwjgl:lwjgl-ovr:${lwjgl3Version}:natives-windows" - - // Native OpenVR/LWJGL support - api "org.lwjgl:lwjgl-openvr:${lwjgl3Version}" - implementation "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-linux" - implementation "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-macos" -} - -javadoc { - // Disable doclint for JDK8+. - if (JavaVersion.current().isJava8Compatible()){ - options.addStringOption('Xdoclint:none', '-quiet') - } -} 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 fa0956b2b2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/app/VRAppState.java +++ /dev/null @@ -1,626 +0,0 @@ -package com.jme3.app; - -/* - * 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. - */ -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 { - public static final String ID = "VRAppState"; - 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(ID); - - 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 contain 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 view manager 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 02ab733230..0000000000 --- a/jme3-vr/src/main/java/com/jme3/app/VRApplication.java +++ /dev/null @@ -1,1504 +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 extend 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(0, 0); - - 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 contain {@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 and 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. - */ - @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.handleErrorMessage(errMsg + "\n" + t.getClass().getSimpleName() + - (t.getMessage() != null ? ": " + t.getMessage() : "")); - } else { - JmeSystem.handleErrorMessage(errMsg); - } - } - - stop(); // stop the application - } - - - /** - * Force the focus gain for the application. Internal use only. - */ - @Override - 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. - */ - @Override - public void loseFocus(){ - if (lostFocusBehavior != LostFocusBehavior.Disabled){ - if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) { - paused = true; - } - context.setAutoFlushFrames(false); - } - } - - /** - * Reshape the display window. Internal use only. - */ - @Override - public void reshape(int w, int h){ - if (renderManager != null) { - renderManager.notifyReshape(w, h); - } - } - - /** - * Request the application to close. Internal use only. - */ - @Override - 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. - */ - @Override - 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. - */ - @Override - 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. - */ - @Override - 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. - */ - @Override - 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() - */ - @Override - 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) - */ - @Override - 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 (!JmeSystem.showSettingsDialog(settings, loadSettings)) { - logger.config("Starting application [SUCCESS]"); - return; - } - - - 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) - */ - @Override - 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 dedicated 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 ) { - // Update geometric state here only if GUI is in manual mode, or not in VR. - // Otherwise it will get updated automatically in the view-manager update. - 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(){ - // acquire 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 set 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.useJoysticks()){ - 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). - */ - @Override - 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 - */ - @Override - 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. - */ - @Override - public void setAppProfiler(AppProfiler prof) { - return; - } - - /** - * Returns the current AppProfiler hook, or null if none is set. - */ - @Override - public AppProfiler getAppProfiler() { - return null; - } -} 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 d6ed724766..0000000000 --- a/jme3-vr/src/main/java/com/jme3/app/VRConstants.java +++ /dev/null @@ -1,174 +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 Oculus 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. - * - * Deprecated as only the lwjgl OpenVr version has been upgraded to modern action based inputs - * - * @see #SETTING_VRAPI_OSVR_VALUE - * @see #SETTING_VRAPI_OPENVR_LWJGL_VALUE - * @see #SETTING_VRAPI_OCULUSVR_VALUE - */ - @Deprecated - public static final int SETTING_VRAPI_OPENVR_VALUE = 1; - - /** - * The identifier of the OSVR system. - * - * Deprecated as an OpenVr system should be used instead for a non vender specific api - * - * @see #SETTING_VRAPI_OPENVR_VALUE - * @see #SETTING_VRAPI_OPENVR_LWJGL_VALUE - * @see #SETTING_VRAPI_OCULUSVR_VALUE - */ - @Deprecated - 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. - * - * Deprecated as an OpenVr system should be used instead (and the rift itself is discontinued) - * - * @see #SETTING_VRAPI_OPENVR_VALUE - * @see #SETTING_VRAPI_OSVR_VALUE - * @see #SETTING_VRAPI_OPENVR_LWJGL_VALUE - */ - @Deprecated - public static final int SETTING_VRAPI_OCULUSVR_VALUE = 4; - - /** - * A private constructor to inhibit instantiation of this class. - */ - private VRConstants() { - } -} 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 6dd41d6cde..0000000000 --- a/jme3-vr/src/main/java/com/jme3/app/VREnvironment.java +++ /dev/null @@ -1,543 +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( 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(0, 0); - } - } - } 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; - - // Instantiate 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 instantiate 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 Oculus 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); - }else{ - vrBinding = VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE; //this is the binding that is best supported so makes sense to be the default - } - - 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 6d4f5d6691..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/lwjgl/GlfwKeyInputVR.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.jme3.input.lwjgl; - -/* - * 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. - */ - -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; - } - - @Override - 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; - } - - @Override - public void update() { - if (!context.isRenderable()) { - return; - } - - while (!keyInputEvents.isEmpty()) { - listener.onKeyEvent(keyInputEvents.poll()); - } - } - - @Override - public void destroy() { - if (!context.isRenderable()) { - return; - } - - keyCallback.free(); - - logger.fine("Keyboard destroyed."); - } - - @Override - public boolean isInitialized() { - return initialized; - } - - @Override - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - @Override - public long getInputTimeNanos() { - return (long) (glfwGetTime() * 1000000000); - } - - @Override - public String getKeyName(int jmeKey) { - int glfwKey = GlfwKeyMap.fromJmeKeyCode(jmeKey); - return glfwGetKeyName(glfwKey, 0); - } - -} 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 8566ca3a04..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/lwjgl/GlfwMouseInputVR.java +++ /dev/null @@ -1,332 +0,0 @@ -package com.jme3.input.lwjgl; - -/* - * 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. - */ - -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 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.

      - * 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); - } - - @Override - 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. - */ - @Override - 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()); - } - - @Override - 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 6be4cae9fb..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/AbstractVRMouseManager.java +++ /dev/null @@ -1,226 +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 overridden 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 mouse image - 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 03868cedf7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/AbstractVRViewManager.java +++ /dev/null @@ -1,226 +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 extend 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. - */ - @Override - 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 - */ - @Override - 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){ - // set up 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, and 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, light scattering 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/AnalogActionState.java b/jme3-vr/src/main/java/com/jme3/input/vr/AnalogActionState.java deleted file mode 100644 index 8cf519ff0b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/AnalogActionState.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jme3.input.vr; - -public class AnalogActionState{ - - /** - * The X coordinate of the analog data (typically between -1 and 1 for joystick coordinates or 0 and 1 for - * trigger pulls) - */ - public final float x; - - /** - * The Y coordinate of the analog data (typically between -1 and 1) - * - * Will be zero if the analog action doesn't have at least 2 dimensions - */ - public final float y; - - /** - * The Z coordinate of the analog data (typically between -1 and 1) - * - * Will be zero if the analog action doesn't have at least 3 dimensions - */ - public final float z; - - /** - * The change in the X coordinate since the last frame - */ - public final float deltaX; - - /** - * The change in the Y coordinate since the last frame - */ - public final float deltaY; - - /** - * The change in the Z coordinate since the last frame - */ - public final float deltaZ; - - public AnalogActionState(float x, float y, float z, float deltaX, float deltaY, float deltaZ){ - this.x = x; - this.y = y; - this.z = z; - this.deltaX = deltaX; - this.deltaY = deltaY; - this.deltaZ = deltaZ; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/DigitalActionState.java b/jme3-vr/src/main/java/com/jme3/input/vr/DigitalActionState.java deleted file mode 100644 index 1da865eecf..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/DigitalActionState.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.jme3.input.vr; - -public class DigitalActionState{ - - /** - * The current value of this action - */ - public final boolean state; - - /** - * If since the last loop the value of this action has changed - */ - public final boolean changed; - - public DigitalActionState(boolean state, boolean changed){ - this.state = state; - this.changed = changed; - } -} 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 8dfe9b9365..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/HmdType.java +++ /dev/null @@ -1,68 +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, - - /** - * Oculus 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 (formerly 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 e2afa0e789..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRAPI.java +++ /dev/null @@ -1,183 +0,0 @@ -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 179dbb1b08..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 describes 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 113a23c1f2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRInputAPI.java +++ /dev/null @@ -1,390 +0,0 @@ -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 { - - /** - * Registers an action manifest. An action manifest is a file that defines "actions" a player can make. - * (An action is an abstract version of a button press). The action manifest may then also include references to - * further files that define default mappings between those actions and physical buttons on the VR controllers. - * - * Note that registering an actions manifest will deactivate legacy inputs. i.e. methods such as {@link #isButtonDown} - * will no longer work - * - * See https://github.com/ValveSoftware/openvr/wiki/Action-manifest for documentation on how to create an - * action manifest - * - * This option is only relevant to OpenVR - * - * @param actionManifestAbsolutePath - * the absolute file path to an actions manifest - * @param startingActiveActionSet - * the actions in the manifest are divided into action sets (groups) by their prefix (e.g. "/actions/main"). - * These action sets can be turned off and on per frame. This argument sets the action set that will be - * active now. The active action sets can be later be changed by calling {@link #setActiveActionSet}. - * Note that at present only a single set at a time is supported - * - */ - default void registerActionManifest( String actionManifestAbsolutePath, String startingActiveActionSet ){ - throw new UnsupportedOperationException("Action manifests are not supported for the currently used VR API"); - } - - /** - * Updates the active action set (the action group that will have their states available to be polled). - * - * Note that this update will not take effect until the next loop - * Note that at present only a single set at a time is supported - * - * @param activeActionSet - * the actions in the manifest are divided into action sets (groups) by their prefix (e.g. "/actions/main"). - * These action sets can be turned off and on per frame. This argument sets the action set that will be - * active now. - */ - default void setActiveActionSet( String activeActionSet ){ - throw new UnsupportedOperationException("Action manifests are not supported for the currently used VR API"); - } - - /** - * Gets the current state of the action (abstract version of a button press). - * - * This is called for digital style actions (a button is pressed, or not) - * - * This method is commonly called when it's not important which hand the action is bound to (e.g. if a button press - * is opening your inventory that could be bound to either left or right hand and that would not matter). - * - * If the handedness matters use {@link #getDigitalActionState(String, String)} - * - * {@link #registerActionManifest} must have been called before using this method. - * - * @param actionName The name of the action. Will be something like /actions/main/in/openInventory - * @return the DigitalActionState that has details on if the state has changed, what the state is etc. - */ - default DigitalActionState getDigitalActionState( String actionName ){ - return getDigitalActionState(actionName, null); - } - - /** - * Gets the current state of the action (abstract version of a button press). - * - * This is called for digital style actions (a button is pressed, or not) - * - * This method is commonly called when it is important which hand the action is found on. For example while - * holding a weapon a button may be bound to "eject magazine" to allow you to load a new one, but that would only - * want to take effect on the hand that is holding the weapon - * - * Note that restrictToInput only restricts, it must still be bound to the input you want to receive the input from in - * the action manifest default bindings. - * - * {@link #registerActionManifest} must have been called before using this method. - * - * @param actionName The name of the action. E.g. /actions/main/in/openInventory - * @param restrictToInput the input to restrict the action to. E.g. /user/hand/right. Or null, which means "any input" - * @return the DigitalActionState that has details on if the state has changed, what the state is etc. - */ - default DigitalActionState getDigitalActionState( String actionName, String restrictToInput ){ - throw new UnsupportedOperationException("Action manifests are not supported for the currently used VR API"); - } - - /** - * Gets the current state of the action (abstract version of a button press). - * - * This is called for analog style actions (most commonly joysticks, but button pressure can also be mapped in analog). - * - * This method is commonly called when it's not important which hand the action is bound to (e.g. if the thumb stick - * is controlling a third-person character in-game that could be bound to either left or right hand and that would - * not matter). - * - * If the handedness matters use {@link #getAnalogActionState(String, String)} - * - * {@link #registerActionManifest} must have been called before using this method. - * - * @param actionName The name of the action. E.g. /actions/main/in/openInventory - * @return the DigitalActionState that has details on if the state has changed, what the state is etc. - */ - default AnalogActionState getAnalogActionState( String actionName ){ - return getAnalogActionState(actionName, null); - } - - /** - * Gets the current state of the action (abstract version of a button press). - * - * This is called for analog style actions (most commonly joysticks, but button pressure can also be mapped in analog). - * - * This method is commonly called when it is important which hand the action is found on. For example an "in universe" - * joystick that has a hat control might (while you are holding it) bind to the on-controller hat, but only on the hand - * holding it - * - * Note that restrictToInput only restricts, it must still be bound to the input you want to receive the input from in - * the action manifest default bindings. - * - * {@link #registerActionManifest} must have been called before using this method. - * - * @param actionName The name of the action. E.g. /actions/main/in/openInventory - * @param restrictToInput the input to restrict the action to. E.g. /user/hand/right. Or null, which means "any input" - * @return the DigitalActionState that has details on if the state has changed, what the state is etc. - */ - default AnalogActionState getAnalogActionState( String actionName, String restrictToInput ){ - throw new UnsupportedOperationException("Action manifests are not supported for the currently used VR API"); - } - - /** - * Check if the given button is down (more generally if the given input type is activated). - * - * @deprecated Use the action-manifest approach instead. See {@link #registerActionManifest}. - * Note: action-manifest will only work with the OpenVR api. - * - * @param controllerIndex the index of the controller to check. - * @param checkButton the button / input to check. - * @return true if the button / input is down / activated and false otherwise. - */ - @Deprecated - public boolean isButtonDown(int controllerIndex, VRInputType checkButton); - - /** - * Check if the given button / input from the given controller has been just pressed / activated. - * - * @deprecated Use the action-manifest approach instead. See {@link #registerActionManifest}. - * Note: action-manifest will only work with the OpenVR api. - * - * @param controllerIndex the index of the controller. - * @param checkButton the button / input to check. - * @return true if the given input from the given controller has just been activated, - * false otherwise. - */ - @Deprecated - public boolean wasButtonPressedSinceLastCall(int controllerIndex, VRInputType checkButton); - - /** - * Reset the current activation of the inputs. After a call to this method, any input activation is - * considered a new activation. - * - * @see #wasButtonPressedSinceLastCall(int, VRInputType) - * - * @deprecated Use the action-manifest approach instead. See {@link #registerActionManifest}. - * Note: action-manifest will only work with the OpenVR api. - */ - @Deprecated - public void resetInputSinceLastCall(); - - /** - * Get the controller axis delta from the last value. - * - * @deprecated Use action-manifest approach instead. See {@link #registerActionManifest}. - * Note: action-manifest will only work with the OpenVR api. - * - * @param controllerIndex the index of the controller. - * @param forAxis the axis. - * @return the controller axis delta from the last call. - */ - @Deprecated - 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}. - * - * @deprecated Use action-manifest approach instead. See {@link #registerActionManifest}. - * Note: action-manifest will only work with the OpenVR api. - * - * @param controllerIndex the index of the controller. - * @param forAxis the axis. - * @return the axis value for the given input on the given controller. - * @see #getAxisRaw(int, VRInputType) - * @see #getAxisMultiplier() - */ - @Deprecated - public Vector2f getAxis(int controllerIndex, VRInputType forAxis); - - /** - * Get the axis value for the given input on the given controller. - * - * @deprecated Use the action-manifest approach. See {@link #registerActionManifest}. - * Note: action-manifest will only work with the OpenVR api. - * - * @param controllerIndex the index of the controller. - * @param forAxis the axis. - * @return the axis value for the given input on the given controller. - * @see #getAxis(int, VRInputType) - */ - @Deprecated - 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 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 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). - * - * @deprecated Use triggerHapticAction instead - it has more options and doesn't use deprecated methods. - * - * @param controllerIndex the index of the controller. - * @param seconds the duration of the pulse in seconds. - */ - @Deprecated - public void triggerHapticPulse(int controllerIndex, float seconds); - - /** - * Triggers a haptic action (aka a vibration). - * - * Note: if you want a haptic action in only one hand, you can either bind the action to one hand in - * the action manifest's standard bindings or bind to both and use - * {@link #triggerHapticAction(String, float, float, float, String)} - * to control which input it gets set to at runtime. - * - * @param actionName The name of the action. Will be something like /actions/main/out/vibrate - * @param duration how long in seconds the - * @param frequency in cycles per second - * @param amplitude between 0 and 1 - */ - default void triggerHapticAction( String actionName, float duration, float frequency, float amplitude){ - triggerHapticAction( actionName, duration, frequency, amplitude, null ); - } - - /** - * Triggers a haptic action (aka a vibration) restricted to just one input (e.g. left or right hand). - * - * Note: restrictToInput only restricts which input is used at runtime. You must still bind the input - * you want to send the haptic to in the action manifest default bindings. - * - * This method is typically used by binding the haptic to both hands, and then deciding at runtime which - * hand it should be sent to. - * - * @param actionName The name of the action. Will be something like /actions/main/out/vibrate - * @param duration how long in seconds the - * @param frequency in cycles per second - * @param amplitude between 0 and 1 - * @param restrictToInput the input to restrict the action to, such as /user/hand/right or - * /user/hand/left. null means "any input". - */ - default void triggerHapticAction( String actionName, float duration, float frequency, float amplitude, String restrictToInput){ - throw new UnsupportedOperationException("Action manifests are not supported for the currently used VR API"); - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/VRInputType.java b/jme3-vr/src/main/java/com/jme3/input/vr/VRInputType.java deleted file mode 100644 index f4e145c708..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRInputType.java +++ /dev/null @@ -1,149 +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 - * - * Deprecated, use the LWJGL openVR bindings and use actions instead - * - */ -@Deprecated -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 b6923388f2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRMouseManager.java +++ /dev/null @@ -1,111 +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 e5133d2e38..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRTrackedController.java +++ /dev/null @@ -1,48 +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 30c4ec2c99..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVR.java +++ /dev/null @@ -1,434 +0,0 @@ -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.*; - -/** - * 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(); - - 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 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"; - } - - @Override - public void setFlipEyes(boolean set) { - flipEyes = set; - } - - @Override - public void printLatencyInfoToConsole(boolean set) { - // not implemented - } - - @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(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() { - VRChaperone.VRChaperone_ResetZeroPose(VR.ETrackingUniverseOrigin_TrackingUniverseSeated); - 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 tempMatrix = new Matrix4f(); - convertSteamVRMatrix3ToMatrix4f(mat, tempMatrix); - tempMatrix.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/LWJGLOpenVRAnalogActionData.java b/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRAnalogActionData.java deleted file mode 100644 index aa702fc13a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRAnalogActionData.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jme3.input.vr.lwjgl_openvr; - -import org.lwjgl.openvr.InputAnalogActionData; - -/** - * This is a set of reusable parts that are used when accessing an analogue action - * (Analogue meaning something like a trigger pull or joystick coordinate) - */ -public class LWJGLOpenVRAnalogActionData{ - - /** - * This is the address string for the action. It will be something like /actions/main/in/openInventory - */ - String actionName; - - /** - * The handle used to request the action's state from LWJGL. - * - * It is how the action is addressed efficiently - */ - long actionHandle; - - /** - * This is a LWJGL object that will have the actions state passed into it. It is mapped to native memory so we - * don't want to keep creating new ones. - */ - InputAnalogActionData actionData; - - public LWJGLOpenVRAnalogActionData(String actionName, long actionHandle, InputAnalogActionData actionData){ - this.actionName = actionName; - this.actionHandle = actionHandle; - this.actionData = actionData; - } -} 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 cce7aec17b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRBounds.java +++ /dev/null @@ -1,50 +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/LWJGLOpenVRDigitalActionData.java b/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRDigitalActionData.java deleted file mode 100644 index 75b1a100ed..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRDigitalActionData.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jme3.input.vr.lwjgl_openvr; - -import org.lwjgl.openvr.InputDigitalActionData; - -/** - * This is a set of reusable parts that are used when accessing a digital action - * (Digital meaning something like a button press, that is either on or off) - */ -public class LWJGLOpenVRDigitalActionData{ - - /** - * This is the address string for the action. It will be something like /actions/main/in/openInventory - */ - String actionName; - - /** - * The handle used to request the action's state from LWJGL. - * - * It is how the action is addressed efficiently - */ - long actionHandle; - - /** - * This is a LWJGL object that will have the actions state passed into it. It is mapped to native memory so we - * don't want to keep creating new ones. - */ - InputDigitalActionData actionData; - - public LWJGLOpenVRDigitalActionData(String actionName, long actionHandle, InputDigitalActionData actionData){ - this.actionName = actionName; - this.actionHandle = actionHandle; - this.actionData = actionData; - } -} 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 04d5e9dd38..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRInput.java +++ /dev/null @@ -1,703 +0,0 @@ -package com.jme3.input.vr.lwjgl_openvr; - -import java.nio.LongBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.AnalogActionState; -import com.jme3.input.vr.DigitalActionState; -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.InputAnalogActionData; -import org.lwjgl.openvr.InputDigitalActionData; -import org.lwjgl.openvr.VR; -import org.lwjgl.openvr.VRActiveActionSet; -import org.lwjgl.openvr.VRControllerState; -import org.lwjgl.openvr.VRInput; -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()); - - /** - * Deprecated as used controller specific values. Should use Actions manifest instead - */ - @Deprecated - 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]; - - /** - * Deprecated as used controller specific values. Should use Actions manifest instead - */ - @Deprecated - private final boolean[][] buttonDown = new boolean[VR.k_unMaxTrackedDeviceCount][16]; - - /** - * A map of the action name to the objects/data required to read states from lwjgl - */ - private final Map digitalActions = new HashMap<>(); - - /** - * A map of the action name to the objects/data required to read states from lwjgl - */ - private final Map analogActions = new HashMap<>(); - - /** - * A map of the action name to the handle of a haptic action - */ - private final Map hapticActionHandles = new HashMap<>(); - - /** - * A map of the action set name to the handle that is used to refer to it when talking to LWJGL - */ - private final Map actionSetHandles = new HashMap<>(); - - /** - * A map of input names (e.g. /user/hand/right) to the handle used to address it. - * - * Note that null is a special case that maps to VR.k_ulInvalidInputValueHandle and means "any input" - */ - private final Map inputHandles = new HashMap<>(); - - private float axisMultiplier = 1f; - - private final Vector3f tempVel = new Vector3f(); - - private final Quaternion tempq = new Quaternion(); - - private final VREnvironment environment; - - private List trackedControllers = null; - - /** - * A lwjgl object that contains handles to the active action sets (is used each frame to tell lwjgl which actions to - * fetch states back for) - */ - private VRActiveActionSet.Buffer activeActionSets; - - InputMode inputMode = InputMode.LEGACY; - - private enum InputMode{ - /** - * Simple bitfield, no way to map new controllers - */ - LEGACY, - /** - * Actions manifest based. - */ - ACTION_BASED; - } - - /** - * 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; - - inputHandles.put(null, VR.k_ulInvalidInputValueHandle); - } - - @Override - public void registerActionManifest(String actionManifestAbsolutePath, String startingActiveActionSets){ - inputMode = InputMode.ACTION_BASED; - int errorCode = VRInput.VRInput_SetActionManifestPath(actionManifestAbsolutePath); - - if ( errorCode != 0 ) - { - logger.warning( "An error code of " + errorCode + " was reported while registering an action manifest" ); - } - setActiveActionSet(startingActiveActionSets); - } - - @Override - public void setActiveActionSet(String actionSet){ - assert inputMode == InputMode.ACTION_BASED : "registerActionManifest must be called before attempting to fetch action states"; - - - long actionSetHandle; - if (actionSetHandles.containsKey(actionSet)){ - actionSetHandle = actionSetHandles.get(actionSet); - }else{ - LongBuffer longBuffer = BufferUtils.createLongBuffer(1); - int errorCode = VRInput.VRInput_GetActionHandle(actionSet, longBuffer); - if ( errorCode != 0 ) - { - logger.warning( "An error code of " + errorCode + " was reported while fetching an action set handle for " + actionSet ); - } - actionSetHandle = longBuffer.get(0); - actionSetHandles.put(actionSet,actionSetHandle); - } - - //Todo: this seems to imply that you could have multiple active action sets at once (Although I was not able to get that to work), allow multiple action sets - activeActionSets = VRActiveActionSet.create(1); - activeActionSets.ulActionSet(actionSetHandle); - activeActionSets.ulRestrictedToDevice(VR.k_ulInvalidInputValueHandle); // both hands - } - - @Override - public DigitalActionState getDigitalActionState(String actionName, String restrictToInput){ - assert inputMode == InputMode.ACTION_BASED : "registerActionManifest must be called before attempting to fetch action states"; - - LWJGLOpenVRDigitalActionData actionDataObjects = digitalActions.get(actionName); - if (actionDataObjects == null){ - //this is the first time the action has been used. We must obtain a handle to it to efficiently fetch it in future - long handle = fetchActionHandle(actionName); - actionDataObjects = new LWJGLOpenVRDigitalActionData(actionName, handle, InputDigitalActionData.create()); - digitalActions.put(actionName, actionDataObjects); - } - int errorCode = VRInput.VRInput_GetDigitalActionData(actionDataObjects.actionHandle, actionDataObjects.actionData, getOrFetchInputHandle(restrictToInput)); - - if (errorCode == VR.EVRInputError_VRInputError_WrongType){ - throw new RuntimeException("Attempted to fetch a non-digital state as if it is digital"); - }else if (errorCode!=0){ - logger.warning( "An error code of " + errorCode + " was reported while fetching an action state for " + actionName ); - } - - return new DigitalActionState(actionDataObjects.actionData.bState(), actionDataObjects.actionData.bChanged()); - } - - @Override - public AnalogActionState getAnalogActionState(String actionName, String restrictToInput ){ - assert inputMode == InputMode.ACTION_BASED : "registerActionManifest must be called before attempting to fetch action states"; - - LWJGLOpenVRAnalogActionData actionDataObjects = analogActions.get(actionName); - if (actionDataObjects == null){ - //this is the first time the action has been used. We must obtain a handle to it to efficiently fetch it in future - long handle = fetchActionHandle(actionName); - actionDataObjects = new LWJGLOpenVRAnalogActionData(actionName, handle, InputAnalogActionData.create()); - analogActions.put(actionName, actionDataObjects); - } - int errorCode = VRInput.VRInput_GetAnalogActionData(actionDataObjects.actionHandle, actionDataObjects.actionData, getOrFetchInputHandle(restrictToInput)); - - if (errorCode == VR.EVRInputError_VRInputError_WrongType){ - throw new RuntimeException("Attempted to fetch a non-analog state as if it is analog"); - }else if (errorCode!=0){ - logger.warning( "An error code of " + errorCode + " was reported while fetching an action state for " + actionName ); - } - - return new AnalogActionState(actionDataObjects.actionData.x(), actionDataObjects.actionData.y(), actionDataObjects.actionData.z(), actionDataObjects.actionData.deltaX(), actionDataObjects.actionData.deltaY(), actionDataObjects.actionData.deltaZ()); - } - - @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) { - assert inputMode != InputMode.ACTION_BASED : "registerActionManifest has been called, legacy button access disabled"; - 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 + " bound."); - } - - 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 = 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 triggerHapticAction(String actionName, float duration, float frequency, float amplitude, String restrictToInput ){ - long hapticActionHandle; - if (!hapticActionHandles.containsKey(actionName)){ - //this is the first time the action has been used. We must obtain a handle to it to efficiently fetch it in future - hapticActionHandle = fetchActionHandle(actionName); - hapticActionHandles.put(actionName, hapticActionHandle); - }else{ - hapticActionHandle = hapticActionHandles.get(actionName); - } - - VRInput.VRInput_TriggerHapticVibrationAction(hapticActionHandle, 0, duration, frequency, amplitude, getOrFetchInputHandle(restrictToInput)); - } - - @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) { - switch(inputMode){ - case ACTION_BASED: - int errorCode = VRInput.VRInput_UpdateActionState(activeActionSets, VRActiveActionSet.SIZEOF); - if(errorCode!=0){ - logger.warning("An error code of " + errorCode + " was returned while upding the action states"); - } - break; - case LEGACY: - for (int i = 0; i < controllerCount; i++) { - int index = controllerIndex[i]; - VRSystem.VRSystem_GetControllerState(index, cStates[index], 64); - cStates[index].ulButtonPressed(); - cStates[index].rAxis(); - } - break; - } - - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - - } - - /** - * Converts an action name (as it appears in the action manifest) to a handle (long) that the rest of the - * lwjgl (and openVR) wants to talk in - * @param actionName The name of the action. Will be something like /actions/main/in/openInventory - * @return a long that is the handle that can be used to refer to the action - */ - private long fetchActionHandle( String actionName ){ - LongBuffer longBuffer = BufferUtils.createLongBuffer(1); - int errorCode = VRInput.VRInput_GetActionHandle(actionName, longBuffer); - if (errorCode !=0 ){ - logger.warning( "An error code of " + errorCode + " was reported while registering an action manifest" ); - } - return longBuffer.get(0); - } - - /** - * Given an input name returns the handle to address it. - * - * If a cached handle is available it is returned, if not it is fetched from openVr - * - * @param inputName the input name, e.g. /user/hand/right. Or null, which means "any input" - * @return the input handle - */ - public long getOrFetchInputHandle( String inputName ){ - if(!inputHandles.containsKey(inputName)){ - LongBuffer longBuffer = BufferUtils.createLongBuffer(1); - - int errorCode = VRInput.VRInput_GetInputSourceHandle(inputName, longBuffer); - if (errorCode !=0 ){ - logger.warning( "An error code of " + errorCode + " was reported while fetching an input manifest" ); - } - long handle = longBuffer.get(0); - inputHandles.put(inputName, handle); - } - - return inputHandles.get(inputName); - } - -} \ 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 a21b2e3052..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRMouseManager.java +++ /dev/null @@ -1,107 +0,0 @@ -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 c0705f0864..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRTrackedController.java +++ /dev/null @@ -1,96 +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 underlying 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 2b658e53f4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRViewManager.java +++ /dev/null @@ -1,569 +0,0 @@ -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 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 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 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."); - } - } - - /** - * 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 - */ - @Override - 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 with the distortion mesh as the main camera's scene. - */ - private void setupVRScene() { - - if (environment != null) { - if (environment.getApplication() != null) { - // no special scene to set up 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(), 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 fc52ee467a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusMouseManager.java +++ /dev/null @@ -1,106 +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 75c14c1647..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusVR.java +++ /dev/null @@ -1,663 +0,0 @@ -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, etcetera) - */ - 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 width=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; - } - - @Override - 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; - } - - @Override - public void printLatencyInfoToConsole(boolean set) { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - @Override - public void setFlipEyes(boolean set) { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - @Override - public Void getCompositor() { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - @Override - 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 matrices) - - 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 f2ce772fbb..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[][] lastAxes; - - /** - * 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]; - lastAxes = 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, axes - 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 tempQuaternion = new Quaternion(); // TODO move to class scope? - if (obs instanceof Camera) { - tempQuaternion.set(((Camera) obs).getRotation()); - } else { - tempQuaternion.set(((Spatial) obs).getWorldRotation()); - } - - return tempQuaternion.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 = lastAxes[controllerIndex][index]; - if (last == null) { - last = lastAxes[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 6ebbab1cf9..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 target view buffer 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 4489bd9f99..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVR.java +++ /dev/null @@ -1,584 +0,0 @@ -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(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 tempMatrix = new Matrix4f(); - convertSteamVRMatrix3ToMatrix4f(mat, tempMatrix); - tempMatrix.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 ed53274888..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRBounds.java +++ /dev/null @@ -1,56 +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 6e4e536aec..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRInput.java +++ /dev/null @@ -1,498 +0,0 @@ -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 tempQuaternion = 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 ) { - tempQuaternion.set(((Camera)obs).getRotation()); - } else { - tempQuaternion.set(((Spatial)obs).getWorldRotation()); - } - - return tempQuaternion.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 bcd521e891..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRTrackedController.java +++ /dev/null @@ -1,95 +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 underlying 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 c6d0237321..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRViewManager.java +++ /dev/null @@ -1,733 +0,0 @@ -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 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 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 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 with the distortion mesh as the main camera's scene. - */ - private void setupVRScene(){ - if (environment != null){ - if (environment.getApplication() != null){ - // no special scene to set up 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."); - } - } - - /** - * Set up 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 set up 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 c8da65a4f1..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVR.java +++ /dev/null @@ -1,459 +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 context - 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 44bdf97e10..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVRInput.java +++ /dev/null @@ -1,347 +0,0 @@ -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 a3310c67e2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVRViewManager.java +++ /dev/null @@ -1,867 +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 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 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 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. - */ - @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(); - 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. - */ - @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(); - } - - 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(); - 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 with the distortion mesh as the main camera's scene. - */ - private void setupVRScene(){ - if (environment != null){ - if (environment.getApplication() != null){ - // no special scene to set up 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. - */ - @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); - 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 - */ - @Override - 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. - */ - @Override - public void syncScreenProcessing(ViewPort sourceViewport) { - if( getRightViewPort() == null ){ - return; - } - - if (environment != null){ - if (environment.getApplication() != null){ - // set up 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, light scattering 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."); - } - } - - /** - * Set up 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 set up 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 82c45001dd..0000000000 --- a/jme3-vr/src/main/java/com/jme3/post/CartoonSSAO.java +++ /dev/null @@ -1,155 +0,0 @@ -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 * (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 d8d2778742..0000000000 --- a/jme3-vr/src/main/java/com/jme3/post/FilterUtil.java +++ /dev/null @@ -1,48 +0,0 @@ -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 { - /** - * A private constructor to inhibit instantiation of this class. - */ - private 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 b2250e8d76..0000000000 --- a/jme3-vr/src/main/java/com/jme3/post/PreNormalCaching.java +++ /dev/null @@ -1,72 +0,0 @@ -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; - - /** - * A private constructor to inhibit instantiation of this class. - */ - private PreNormalCaching() { - } - - /** - * 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(),true, false); - } else { - // let's 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 e2911eba6f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/scene/CenterQuad.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -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 com.jme3.scene.shape.Quad because it puts - * (0,0,0) at the rectangle's center instead of in a corner. - * - * @author Kirill Vainer - * @deprecated use com.jme3.scene.shape.CenterQuad - */ -@Deprecated -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(); - } - - /** - * For serialization only. Do not use. - */ - protected CenterQuad() { - } - - 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(); - } - - /** - * 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); - } -} 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 3144a417e0..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowFilterVR.java +++ /dev/null @@ -1,320 +0,0 @@ -package com.jme3.shadow; - -/* - * 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. - */ - -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.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 edge 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(); - } - - /** - * 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 7338864dd8..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowRendererVR.java +++ /dev/null @@ -1,839 +0,0 @@ -package com.jme3.shadow; - -/* - * 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. - */ - -import com.jme3.asset.AssetManager; -import com.jme3.export.*; -import com.jme3.light.LightFilter; -import com.jme3.light.NullLightFilter; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.ColorRGBA; -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.texture.FrameBuffer.FrameBufferTarget; -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 { - private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); - 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 prevents 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].setDepthTarget(FrameBufferTarget.newTarget(shadowMaps[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 debugging purposes - 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 IllegalArgumentException("filterMode cannot be null"); - } - - 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 - */ - @Override - 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 - */ - @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 - */ - 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") - @Override - 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 and disables the lightfilter - LightFilter tmpLightFilter = renderManager.getLightFilter(); - renderManager.setLightFilter(NULL_LIGHT_FILTER); - viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true); - renderManager.setLightFilter(tmpLightFilter); - renderManager.setForcedRenderState(null); - } - boolean debugfrustums = false; - - /** - * 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); - - @Override - 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(); - //If some materials in the scene do not have a post shadow technique, use the fallback 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) { - //iterate through 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 technique, - //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); - - @Override - public void preFrame(float tpf) { - } - - @Override - public void cleanup() { - } - - @Override - 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 edge thickness. Default is 1. Setting it to lower values - * can help to reduce the jagged effect of the shadow edges. - * @param edgesThickness the shadow edge 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; } - - /** - * 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) - */ - @Override - public void read(JmeImporter im) throws IOException { - InputCapsule ic = 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) - */ - @Override - public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = 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 7d9adc02f6..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/DirectionalLightShadowFilterVR.java +++ /dev/null @@ -1,172 +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 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 - * - * 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, - * etcetera) - * @param nbSplits the number of shadow maps rendered (More shadow maps yield - * better quality, fewer fps.) - */ - public DirectionalLightShadowFilterVR(AssetManager assetManager, int shadowMapSize, int nbSplits) { - super(assetManager, shadowMapSize, new DirectionalLightShadowRendererVR(assetManager, shadowMapSize, nbSplits)); - } - - /** - * Creates a DirectionalLight shadow filter. More info on the - * technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html. - * - * @param assetManager the application's asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512, 1024, 2048, etc...) - * @param nbSplits the number of shadow maps rendered (More shadow maps yield - * better quality, fewer 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(); - } - - /** - * 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) { - 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 loss of shadow quality in 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 d0676eebb8..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/DirectionalLightShadowRendererVR.java +++ /dev/null @@ -1,308 +0,0 @@ -package com.jme3.shadow; - -/* - * 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. - */ - -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 information on this read 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(); - } - - /** - * Creates a DirectionalLight shadow renderer. More info on the technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html - * - * @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 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; - } - - /** - * 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) { - 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 loss of shadow quality in 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 = 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 = 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 a848409f70..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/InstancedDirectionalShadowFilter.java +++ /dev/null @@ -1,66 +0,0 @@ -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}. - * @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, etcetera) - * @param nbSplits the number of shadow maps rendered (More shadow maps yield - * better quality, fewer frames per second.) - * @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 39ecdaed96..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/VRDirectionalLightShadowRenderer.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.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 information on this read http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
      - * - * @author Rémy Bouquet aka Nehon - */ -public class VRDirectionalLightShadowRenderer extends DirectionalLightShadowRenderer { - - /** - * Create an 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 (More shadow maps - * result in higher quality, fewer 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 f88400e2bd..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/AppOverrideKeys_t.java +++ /dev/null @@ -1,50 +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(); - } - @Override - 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 5c3d4fc08e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/COpenVRContext.java +++ /dev/null @@ -1,110 +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(); - } - @Override - 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 5be90516cb..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/CVRSettingHelper.java +++ /dev/null @@ -1,43 +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(); - } - @Override - 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 ebbc398bd6..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/CameraVideoStreamFrameHeader_t.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: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(); - } - @Override - 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 3b426b7b8f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_CumulativeStats.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: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(); - } - @Override - 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 ede38a055b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_FrameTiming.java +++ /dev/null @@ -1,54 +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(); - } - @Override - 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 3afba14f1d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_OverlaySettings.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: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(); - } - @Override - 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 c902ad237a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/D3D12TextureData_t.java +++ /dev/null @@ -1,54 +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(); - } - @Override - 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 59e0ab3dd1..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/DistortionCoordinates_t.java +++ /dev/null @@ -1,64 +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(); - } - @Override - 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 4b89bc4746..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/DriverDirectMode_FrameTiming.java +++ /dev/null @@ -1,42 +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(); - } - @Override - 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 2844685d11..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HiddenAreaMesh_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: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(); - } - @Override - 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 6387f1f0f7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdColor_t.java +++ /dev/null @@ -1,40 +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(); - } - @Override - 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 25e7f8d670..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix33_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: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(); - } - @Override - 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 175bc07f75..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix34_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: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(); - } - @Override - 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 2243e8f5fa..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix44_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: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(); - } - @Override - 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 28abf8f754..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuad_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: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(); - } - @Override - 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 d26c2acfef..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuaternion_t.java +++ /dev/null @@ -1,40 +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(); - } - @Override - 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 b24d5c071d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuaternionf_t.java +++ /dev/null @@ -1,40 +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(); - } - @Override - 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 a8c25431db..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdRect2_t.java +++ /dev/null @@ -1,42 +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(); - } - @Override - 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 f66240574d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector2_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: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(); - } - @Override - 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 92ad299605..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector3_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: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(); - } - @Override - 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 7bde50647c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector3d_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: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(); - } - @Override - 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 1db01177cf..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector4_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: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(); - } - @Override - 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 1cadc4b7f2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/ImuSample_t.java +++ /dev/null @@ -1,46 +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(); - } - @Override - 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 7f7a2a6922..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputAnalogActionData_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: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(); - } - @Override - 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 3b212e44af..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputDigitalActionData_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: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(); - } - @Override - 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 eff72867ae..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputOriginInfo_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: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(); - } - @Override - 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 bc375f1a57..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputPoseActionData_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: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(); - } - @Override - 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 8d1f26514b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputSkeletalActionData_t.java +++ /dev/null @@ -1,40 +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(); - } - @Override - 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 8c994f84ba..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/IntersectionMaskCircle_t.java +++ /dev/null @@ -1,38 +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(); - } - @Override - 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 e33a836db9..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/IntersectionMaskRectangle_t.java +++ /dev/null @@ -1,40 +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(); - } - @Override - 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 b9aaffe311..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/NotificationBitmap_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: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(); - } - @Override - 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 2d075cf80a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/OpenVRUtil.java +++ /dev/null @@ -1,779 +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 { - /** - * A private constructor to inhibit instantiation of this class. - */ - private 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 7e72d93c88..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_ComponentState_t.java +++ /dev/null @@ -1,46 +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(); - } - @Override - 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 cf1eac008f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_ControllerMode_State_t.java +++ /dev/null @@ -1,34 +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(); - } - @Override - 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 41670df53b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_TextureMap_t.java +++ /dev/null @@ -1,46 +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(); - } - @Override - 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 17fac6dd53..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_Vertex_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: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(); - } - @Override - 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 9828f148fe..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_t.java +++ /dev/null @@ -1,59 +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(); - } - @Override - 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 2d3fc2cfdb..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/SpatialAnchorPose_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 fc6cfeb95e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Texture_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: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(); - } - @Override - 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 d2a39b1184..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/TrackedDevicePose_t.java +++ /dev/null @@ -1,57 +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(); - } - @Override - 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 49720d038e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRActiveActionSet_t.java +++ /dev/null @@ -1,50 +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(); - } - @Override - 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 e6e7626d24..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRBoneTransform_t.java +++ /dev/null @@ -1,42 +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(); - } - @Override - 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 41f0e13665..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRControllerAxis_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 9adc41c7d2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRControllerState_t.java +++ /dev/null @@ -1,50 +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(); - } - @Override - 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 845ca8799c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_ApplicationLaunch_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 92e97a4580..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Chaperone_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 e7959582cc..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Controller_t.java +++ /dev/null @@ -1,34 +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(); - } - @Override - 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 f5e45f5b24..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_DualAnalog_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: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(); - } - @Override - 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 5a54cdd582..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_EditingCameraSurface_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 7175e4c862..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_HapticVibration_t.java +++ /dev/null @@ -1,42 +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(); - } - @Override - 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 427eddfbc2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_InputActionManifestLoad_t.java +++ /dev/null @@ -1,40 +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(); - } - @Override - 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 9b88905847..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_InputBindingLoad_t.java +++ /dev/null @@ -1,42 +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(); - } - @Override - 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 afc05f74ea..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Ipd_t.java +++ /dev/null @@ -1,34 +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(); - } - @Override - 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 9cf8c64ed3..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Keyboard_t.java +++ /dev/null @@ -1,46 +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(); - } - @Override - 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 b0fe1b8c2e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_MessageOverlay_t.java +++ /dev/null @@ -1,34 +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(); - } - @Override - 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 d20db8c841..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Mouse_t.java +++ /dev/null @@ -1,38 +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(); - } - @Override - 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 fa67bd5e7c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Notification_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 683c485ab7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Overlay_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 13f14852cc..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_PerformanceTest_t.java +++ /dev/null @@ -1,34 +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(); - } - @Override - 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 0e7f9ae2fb..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Process_t.java +++ /dev/null @@ -1,38 +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(); - } - @Override - 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 be84aa3129..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Property_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: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(); - } - @Override - 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 13f0c8d822..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Reserved_t.java +++ /dev/null @@ -1,40 +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(); - } - @Override - 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 b0511dbc24..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_ScreenshotProgress_t.java +++ /dev/null @@ -1,34 +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(); - } - @Override - 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 c35acd1390..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Screenshot_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 1d29ea839b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Scroll_t.java +++ /dev/null @@ -1,38 +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(); - } - @Override - 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 cb7c9f62c5..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_SeatedZeroPoseReset_t.java +++ /dev/null @@ -1,34 +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(); - } - @Override - 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 eeb51f2e26..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_SpatialAnchor_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 d16d2c0727..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Status_t.java +++ /dev/null @@ -1,34 +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(); - } - @Override - 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 dd123e0d3b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_TouchPadMove_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: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(); - } - @Override - 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 21d9bdf390..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_WebConsole_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 843f9da821..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_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; -/** - * 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(); - } - @Override - 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 e0396fda97..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionMaskPrimitive_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: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(); - } - @Override - 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 053942a0e3..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionParams_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: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(); - } - @Override - 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 7fa5b020f2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionResults_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: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(); - } - @Override - 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 88c779846c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureBounds_t.java +++ /dev/null @@ -1,40 +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(); - } - @Override - 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 89f42f80be..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureDepthInfo_t.java +++ /dev/null @@ -1,50 +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(); - } - @Override - 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 0601b2b7a3..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithDepth_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 bbe447ba2c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithPoseAndDepth_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 8eff35c066..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithPose_t.java +++ /dev/null @@ -1,36 +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(); - } - @Override - 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 9ac3eb08bf..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRVulkanTextureData_t.java +++ /dev/null @@ -1,59 +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(); - } - @Override - 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 eed61b3e02..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRApplications_FnTable.java +++ /dev/null @@ -1,217 +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(); - } - @Override - 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 5795f54df7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRChaperoneSetup_FnTable.java +++ /dev/null @@ -1,152 +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(); - } - @Override - 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 1a575c6c09..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRChaperone_FnTable.java +++ /dev/null @@ -1,100 +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(); - } - @Override - 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 53a3039437..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRCompositor_FnTable.java +++ /dev/null @@ -1,291 +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(); - } - @Override - 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 13e444730b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRDriverManager_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 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(); - } - @Override - 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 2b04732a73..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRExtendedDisplay_FnTable.java +++ /dev/null @@ -1,60 +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(); - } - @Override - 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 5a6b9291d0..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRIOBuffer_FnTable.java +++ /dev/null @@ -1,77 +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(); - } - @Override - 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 44987cc991..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRInput_FnTable.java +++ /dev/null @@ -1,140 +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(); - } - @Override - 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 7e15f3591d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRNotifications_FnTable.java +++ /dev/null @@ -1,52 +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(); - } - @Override - 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 f9405bb07d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVROverlay_FnTable.java +++ /dev/null @@ -1,526 +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(); - } - @Override - 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 3d448c307e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRRenderModels_FnTable.java +++ /dev/null @@ -1,146 +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(); - } - @Override - 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 1533e094c8..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRResources_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 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(); - } - @Override - 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 058637d0d4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRScreenshots_FnTable.java +++ /dev/null @@ -1,92 +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(); - } - @Override - 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 ebbe247fb9..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSettings_FnTable.java +++ /dev/null @@ -1,103 +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(); - } - @Override - 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 3b28c8da19..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSpatialAnchors_FnTable.java +++ /dev/null @@ -1,68 +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(); - } - @Override - 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 8257d23a4f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSystem_FnTable.java +++ /dev/null @@ -1,317 +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(); - } - @Override - 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 42ba86816b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRTrackedCamera_FnTable.java +++ /dev/null @@ -1,105 +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(); - } - @Override - 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 0ec3f7498a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java +++ /dev/null @@ -1,302 +0,0 @@ -package com.jme3.system.lwjgl; - -/* - * 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. - */ -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; - - /** - * 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 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 (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary(NativeLibraries.BulletJme.getName(), 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.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.initialize(); - } else { - throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); - } - - if (capabilities.GL_ARB_debug_output && settings.isGraphicsDebug()) { - 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/LwjglWindowVR.java b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglWindowVR.java deleted file mode 100644 index 5c21cd6e92..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglWindowVR.java +++ /dev/null @@ -1,621 +0,0 @@ -package com.jme3.system.lwjgl; - -/* - * 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. - */ - -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; - - // reusable arrays for GLFW calls - final private int width[] = new int[1]; - final private int height[] = new int[1]; - - /** - * Create a new wrapper class over the GLFW framework in LWJGL 3. - * @param type the {@link com.jme3.system.JmeContext.Type type} of the display. - */ - 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 - */ - @Override - public JmeContext.Type getType() { - return type; - } - - /** - * Set the title if it's a windowed display - * - * @param title the title to set - */ - @Override - public void setTitle(final String title) { - if (created.get() && window != NULL) { - glfwSetWindowTitle(window, title); - } - } - - /** - * Restart if it's a windowed or full-screen display. - */ - @Override - 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 glfwSwapBuffers(). - // 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(); - } - - @Override - public JoyInput getJoyInput() { - if (joyInput == null) { - joyInput = new GlfwJoystickInput(); - } - return joyInput; - } - - @Override - public MouseInput getMouseInput() { - if (mouseInput == null) { - mouseInput = new GlfwMouseInputVR(this); - } - return mouseInput; - } - - @Override - public KeyInput getKeyInput() { - if (keyInput == null) { - keyInput = new GlfwKeyInputVR(this); - } - - return keyInput; - } - - @Override - public TouchInput getTouchInput() { - return null; - } - - @Override - public void setAutoFlushFrames(boolean enabled) { - this.autoFlush = enabled; - } - - @Override - 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; - } - - /** - * Returns the height of the framebuffer. - * - * @return the height (in pixels) - */ - @Override - public int getFramebufferHeight() { - glfwGetFramebufferSize(window, width, height); - int result = height[0]; - return result; - } - - /** - * Returns the width of the framebuffer. - * - * @return the width (in pixels) - */ - @Override - public int getFramebufferWidth() { - glfwGetFramebufferSize(window, width, height); - int result = width[0]; - return result; - } - - /** - * Returns the screen X coordinate of the left edge of the content area. - * - * @return the screen X coordinate - */ - @Override - public int getWindowXPosition() { - glfwGetWindowPos(window, width, height); - int result = width[0]; - return result; - } - - /** - * Returns the screen Y coordinate of the top edge of the content area. - * - * @return the screen Y coordinate - */ - @Override - public int getWindowYPosition() { - glfwGetWindowPos(window, width, height); - int result = height[0]; - return result; - } - - // TODO: Implement support for window icon when GLFW supports it. - /* - 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 c6ba614d1d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AccelerationReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 379a7972b4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AccelerationState.java +++ /dev/null @@ -1,49 +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(); - } - @Override - 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 8b5aca7d1c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AnalogReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 c7a3821dd7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AngularAccelerationReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 bb526b7b1c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AngularVelocityReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 4988433679..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_ButtonReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 70af9a36ba..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_DirectionReport.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 f72a176f70..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker2DReport.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 93af317c37..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker3DReport.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 29da57b206..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker3DState.java +++ /dev/null @@ -1,49 +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(); - } - @Override - 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 0ad5f64361..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTrackerBlinkReport.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 07cb64c949..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_IncrementalQuaternion.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 4baffbd65d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_LinearAccelerationReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 c40b3e74ae..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_LinearVelocityReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 8c05d755a8..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Location2DReport.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 47cb3d563a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_NaviPositionReport.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 ff07a8a0a2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_NaviVelocityReport.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 7b8dc7bde0..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_OrientationReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 5dacd27388..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Pose3.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 e39eea50f6..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_PoseReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 d93fc4a1fe..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_PositionReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 72952297a1..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Quaternion.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 7427d14461..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Vec2.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 b3b2ed8893..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Vec3.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 da65aaf62c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_VelocityReport.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 4a4589a077..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_VelocityState.java +++ /dev/null @@ -1,49 +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(); - } - @Override - 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 4289cb200f..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 = 1; - public static final int OSVR_FALSE = 0; - public static final int OSVR_BUTTON_PRESSED = 1; - public static final int OSVR_BUTTON_NOT_PRESSED = 0; - public static final int OSVR_EYE_BLINK = 1; - public static final int OSVR_EYE_NO_BLINK = 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 de59467448..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, etcetera): 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, etcetera): 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 transmitted 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 transmitted 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 8ec8f7cfa4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Pose3.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 4627040206..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Quaternion.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 7aa44a9793..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Vec3.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 384dd827ce..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 = 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 = 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 = 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 = OsvrMatrixConventionsLibrary.OSVR_MatrixMasks.OSVR_MATRIX_MASK_UNSIGNEDZ; - }; - public static final int OSVR_MATRIX_SIZE = 16; - public static final int OSVR_RETURN_SUCCESS = 0; - public static final int OSVR_RETURN_FAILURE = 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 2ebeb8f2fe..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_ProjectionMatrix.java +++ /dev/null @@ -1,45 +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(); - } - @Override - 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 4fca69fc4c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_RGB.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 ea094d4f3e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_RenderParams.java +++ /dev/null @@ -1,53 +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(); - } - @Override - 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 2e38658290..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_ViewportDescription.java +++ /dev/null @@ -1,49 +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(); - } - @Override - 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 f52a83387b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_GraphicsLibraryOpenGL.java +++ /dev/null @@ -1,35 +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(); - } - @Override - 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 185a2d5bb8..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenGLContextParams.java +++ /dev/null @@ -1,57 +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(); - } - @Override - 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 e32b3901f3..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenGLToolkitFunctions.java +++ /dev/null @@ -1,92 +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(); - } - @Override - 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 85e020f6e7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenResultsOpenGL.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_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(); - } - @Override - 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 94d2248543..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_ProjectionMatrix.java +++ /dev/null @@ -1,45 +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(); - } - @Override - 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 c74e15643b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RGB.java +++ /dev/null @@ -1,37 +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(); - } - @Override - 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 7f502dd053..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderBufferOpenGL.java +++ /dev/null @@ -1,35 +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(); - } - @Override - 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 997215f3cb..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderInfoOpenGL.java +++ /dev/null @@ -1,48 +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(); - } - @Override - 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 6df55bdc88..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderParams.java +++ /dev/null @@ -1,53 +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(); - } - @Override - 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 f504476e4c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_ViewportDescription.java +++ /dev/null @@ -1,49 +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(); - } - @Override - 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 e500f89b17..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrtimevalue/OSVR_TimeValue.java +++ /dev/null @@ -1,41 +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(); - } - @Override - 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 eb2731b5c0..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 = 1; - public static final int OSVR_FALSE = 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 d3e32ea972..0000000000 --- a/jme3-vr/src/main/java/com/jme3/util/VRGUIPositioningMode.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.jme3.util; - -/** - * An 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 6bbd603ff6..0000000000 --- a/jme3-vr/src/main/java/com/jme3/util/VRGuiManager.java +++ /dev/null @@ -1,467 +0,0 @@ -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.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.shape.CenterQuad; -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 19a4c5c9be..0000000000 --- a/jme3-vr/src/main/java/com/jme3/util/VRUtil.java +++ /dev/null @@ -1,101 +0,0 @@ -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); - - /** - * A private constructor to inhibit instantiation of this class. - */ - private VRUtil() { - } - - 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/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 9d61ca057e..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.frag +++ /dev/null @@ -1,22 +0,0 @@ -#import "Common/ShaderLib/GLSLCompat.glsllib" -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 734a097e29..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.vert +++ /dev/null @@ -1,17 +0,0 @@ -#import "Common/ShaderLib/GLSLCompat.glsllib" -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 20d195c490..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR15.frag +++ /dev/null @@ -1,24 +0,0 @@ -#import "Common/ShaderLib/GLSLCompat.glsllib" -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 e0c17a91b1..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR15.vert +++ /dev/null @@ -1,17 +0,0 @@ -#import "Common/ShaderLib/GLSLCompat.glsllib" -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 c940462d8a..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.frag +++ /dev/null @@ -1,184 +0,0 @@ -#import "Common/ShaderLib/GLSLCompat.glsllib" -#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/natives-snapshot.properties b/natives-snapshot.properties index 2e7a470a18..b4fc1678a3 100644 --- a/natives-snapshot.properties +++ b/natives-snapshot.properties @@ -1 +1 @@ -natives.snapshot=3d49531fb98c8e41bf39d6a2167d5c3b95557148 +natives.snapshot=a2471007ca569d6ce0f8fc26b92366aa7dcf4a61 diff --git a/settings.gradle b/settings.gradle index 3f6acceb8e..20b3f4f312 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,6 +8,9 @@ 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 @@ -27,6 +30,7 @@ include 'jme3-ios' //native builds include 'jme3-android-native' //cpp +include 'jme3-ios-native' //cpp // Test Data project include 'jme3-testdata' @@ -38,3 +42,5 @@ include 'jme3-awt-dialogs' if(buildAndroidExamples == "true"){ include 'jme3-android-examples' } +include 'jme3-screenshot-tests' + 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 } }