diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 00000000000..5cdd32a553a
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1,7 @@
+;;; Directory Local Variables -*- no-byte-compile: t -*-
+;;; For more information see (info "(emacs) Directory Variables")
+
+((c++-mode . ((c-file-style . "filament")
+ (apheleia-inhibit . t)))
+ (c-mode . ((c-file-style . "filament")
+ (apheleia-inhibit . t))))
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000000..3e033d0c895
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+
+[*.{c,cpp,h,inc,kt,java,js,md}]
+indent_style = space
+indent_size = 4
+max_line_length = 100
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 7faddd90696..69bc29cc63b 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -4,6 +4,8 @@ about: Create a report to help us improve
---
+⚠️ **Issues not using this template will be systematically closed.**
+
**Describe the bug**
A clear and concise description of what the bug is.
@@ -18,8 +20,8 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Logs**
-If applicable, copy logs from your console here. Please *do not*
-use screenshots of logs, copy them as text.
+If applicable, copy **full** logs from your console here. Please *do not*
+use screenshots of logs, copy them as text, use gist or attach an *uncompressed* file.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
diff --git a/.github/actions/android-continuous/action.yml b/.github/actions/android-continuous/action.yml
new file mode 100644
index 00000000000..ecf23fc5244
--- /dev/null
+++ b/.github/actions/android-continuous/action.yml
@@ -0,0 +1,17 @@
+name: 'Android Continuous'
+inputs:
+ build-abi:
+ description: 'The target platform ABI'
+ required: true
+ default: 'armeabi-v7a'
+runs:
+ using: "composite"
+ steps:
+ - uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ - name: Run build script
+ run: |
+ cd build/android && printf "y" | ./build.sh continuous ${{ inputs.build-abi }}
+ shell: bash
diff --git a/.github/actions/ubuntu-apt-add-src/action.yml b/.github/actions/ubuntu-apt-add-src/action.yml
new file mode 100644
index 00000000000..c104574a632
--- /dev/null
+++ b/.github/actions/ubuntu-apt-add-src/action.yml
@@ -0,0 +1,9 @@
+name: 'ubuntu apt add deb-src'
+runs:
+ using: "composite"
+ steps:
+ - name: "ubuntu apt add deb-src"
+ run: |
+ echo "deb-src http://archive.ubuntu.com/ubuntu jammy main restricted universe" | sudo tee /etc/apt/sources.list.d/my.list
+ sudo apt-get update
+ shell: bash
diff --git a/.github/workflows/android-continuous.yml b/.github/workflows/android-continuous.yml
index d13d80e3e31..3c5d3901f6d 100644
--- a/.github/workflows/android-continuous.yml
+++ b/.github/workflows/android-continuous.yml
@@ -10,30 +10,13 @@ on:
jobs:
build-android:
name: build-android
- runs-on: macos-latest
+ # We intentially use a larger runner here to enable larger disk space
+ # (standard linux runner will fail on disk space and faster build time).
+ runs-on: ubuntu-22.04-32core
steps:
- - uses: actions/checkout@v3.3.0
- - name: Run build script
- run: |
- cd build/android && printf "y" | ./build.sh continuous
- - uses: actions/upload-artifact@v1.0.0
+ - uses: actions/checkout@v4.1.6
+ - name: Run Android Continuous
+ uses: ./.github/actions/android-continuous
with:
- name: filament-android
- path: out/filament-android-release.aar
- - uses: actions/upload-artifact@v1.0.0
- with:
- name: filamat-android-full
- path: out/filamat-android-release.aar
- - uses: actions/upload-artifact@v1.0.0
- with:
- name: filamat-android-lite
- path: out/filamat-android-lite-release.aar
- - uses: actions/upload-artifact@v1.0.0
- with:
- name: gltfio-android-release
- path: out/gltfio-android-release.aar
- - uses: actions/upload-artifact@v1.0.0
- with:
- name: filament-utils-android-release
- path: out/filament-utils-android-release.aar
+ build-abi: armeabi-v7a,arm64-v8a,x86_64
diff --git a/.github/workflows/cocopods-deploy.yml b/.github/workflows/cocopods-deploy.yml
new file mode 100644
index 00000000000..26e08c048df
--- /dev/null
+++ b/.github/workflows/cocopods-deploy.yml
@@ -0,0 +1,30 @@
+name: CocoaPods Deploy
+
+# This must be run after the iOS release job has finished, and the iOS release
+# asset has been uploaded to Github.
+on:
+ workflow_dispatch:
+ inputs:
+ release_tag:
+ description: 'Release tag to deploy (e.g., v1.42.2)'
+ required: true
+ default: 'v1.42.2'
+
+jobs:
+ cocoapods-deploy:
+ name: cocoapods-deploy
+ runs-on: macos-14
+ steps:
+ - name: Check out iOS/CocoaPods directory
+ uses: Bhacaz/checkout-files@49fc3050859046bf4f4873678d46099985640e89
+ with:
+ files: ios/CocoaPods
+ token: ${{ secrets.GITHUB_TOKEN }}
+ branch: ${{ github.event.inputs.release_tag }}
+ - name: Move podspec to root
+ run: mv ios/CocoaPods/*.podspec .
+ - name: Install CocoaPods
+ run: gem install cocoapods
+ - uses: michaelhenry/deploy-to-cocoapods-github-action@745686ab065f90596e0d5cfcf97bb2416d94262e
+ env:
+ COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
diff --git a/.github/workflows/ios-continuous.yml b/.github/workflows/ios-continuous.yml
index c9bc5bd85bc..28b29151596 100644
--- a/.github/workflows/ios-continuous.yml
+++ b/.github/workflows/ios-continuous.yml
@@ -10,14 +10,14 @@ on:
jobs:
build-ios:
name: build-ios
- runs-on: macos-latest
+ runs-on: macos-14-xlarge
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/ios && printf "y" | ./build.sh continuous
- - uses: actions/upload-artifact@v1.0.0
+ - uses: actions/upload-artifact@v4
with:
name: filament-ios
path: out/filament-release-ios.tgz
diff --git a/.github/workflows/linux-continuous.yml b/.github/workflows/linux-continuous.yml
index e5596570f25..bc211e09546 100644
--- a/.github/workflows/linux-continuous.yml
+++ b/.github/workflows/linux-continuous.yml
@@ -10,14 +10,14 @@ on:
jobs:
build-linux:
name: build-linux
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-22.04-16core
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/linux && printf "y" | ./build.sh continuous
- - uses: actions/upload-artifact@v1.0.0
+ - uses: actions/upload-artifact@v4
with:
name: filament-linux
path: out/filament-release-linux.tgz
diff --git a/.github/workflows/mac-continuous.yml b/.github/workflows/mac-continuous.yml
index 80402fa5a26..e0a46ab4ba1 100644
--- a/.github/workflows/mac-continuous.yml
+++ b/.github/workflows/mac-continuous.yml
@@ -10,14 +10,14 @@ on:
jobs:
build-mac:
name: build-mac
- runs-on: macos-latest
+ runs-on: macos-14-xlarge
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/mac && printf "y" | ./build.sh continuous
- - uses: actions/upload-artifact@v1.0.0
+ - uses: actions/upload-artifact@v4
with:
name: filament-mac
path: out/filament-release-darwin.tgz
diff --git a/.github/workflows/npm-deploy.yml b/.github/workflows/npm-deploy.yml
new file mode 100644
index 00000000000..44ef2dbbeec
--- /dev/null
+++ b/.github/workflows/npm-deploy.yml
@@ -0,0 +1,33 @@
+name: Npm Deploy
+
+on:
+ workflow_dispatch:
+ inputs:
+ release_tag:
+ description: 'Release tag to deploy (e.g., v1.42.2)'
+ required: true
+ default: 'v1.42.2'
+
+jobs:
+ npm-deploy:
+ name: npm-deploy
+ runs-on: macos-14
+ steps:
+ - uses: actions/checkout@v4.1.6
+ with:
+ ref: ${{ github.event.inputs.release_tag }}
+ # Setup .npmrc file to publish to npm
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '18.x'
+ registry-url: 'https://registry.npmjs.org'
+ - name: Run build script
+ run: |
+ cd build/web && printf "y" | ./build.sh release
+ - name: Deploy to npm
+ run: |
+ cd out/cmake-webgl-release/web/filament-js
+ npm publish
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
index eed86d2f0a3..28479f236d1 100644
--- a/.github/workflows/presubmit.yml
+++ b/.github/workflows/presubmit.yml
@@ -15,10 +15,10 @@ jobs:
strategy:
matrix:
- os: [macos-latest, ubuntu-18.04]
+ os: [macos-14-xlarge, ubuntu-22.04-16core]
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
- name: Run build script
run: |
WORKFLOW_OS=`echo \`uname\` | sed "s/Darwin/mac/" | tr [:upper:] [:lower:]`
@@ -29,10 +29,10 @@ jobs:
build-windows:
name: build-windows
- runs-on: windows-2019
+ runs-on: win-2019-16core
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
- name: Run build script
run: |
build\windows\build-github.bat presubmit
@@ -40,20 +40,26 @@ jobs:
build-android:
name: build-android
- runs-on: macos-latest
+ runs-on: ubuntu-22.04-16core
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
+ - uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: '17'
- name: Run build script
+ # Only build 1 64 bit target during presubmit to cut down build times during presubmit
+ # Continuous builds will build everything
run: |
- cd build/android && printf "y" | ./build.sh presubmit
+ cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
build-ios:
name: build-iOS
- runs-on: macos-latest
+ runs-on: macos-14-xlarge
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/ios && printf "y" | ./build.sh presubmit
@@ -63,10 +69,25 @@ jobs:
build-web:
name: build-web
- runs-on: macos-latest
+ runs-on: ubuntu-22.04-16core
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/web && printf "y" | ./build.sh presubmit
+
+ test-renderdiff:
+ name: test-renderdiff
+ runs-on: ubuntu-22.04-32core
+
+ steps:
+ - uses: actions/checkout@v4.1.6
+ - uses: ./.github/actions/ubuntu-apt-add-src
+ - name: Run script
+ run: |
+ source ./build/linux/ci-common.sh && bash test/renderdiff/test.sh
+ - uses: actions/upload-artifact@v4
+ with:
+ name: presubmit-renderdiff-result
+ path: ./out/renderdiff_tests
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 92b70224bac..bb9d0394e60 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -31,7 +31,7 @@ jobs:
strategy:
matrix:
- os: [macos-latest, ubuntu-18.04]
+ os: [macos-14-xlarge, ubuntu-22.04-32core]
steps:
- name: Decide Git ref
@@ -41,7 +41,7 @@ jobs:
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- name: Run build script
@@ -65,7 +65,7 @@ jobs:
build-web:
name: build-web
- runs-on: macos-latest
+ runs-on: ubuntu-22.04-16core
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
steps:
@@ -76,7 +76,7 @@ jobs:
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- name: Run build script
@@ -98,7 +98,7 @@ jobs:
build-android:
name: build-android
- runs-on: macos-latest
+ runs-on: ubuntu-22.04-16core
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
steps:
@@ -109,24 +109,27 @@ jobs:
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
+ - uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: '17'
- name: Run build script
env:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
- cd build/android && printf "y" | ./build.sh release
+ cd build/android && printf "y" | ./build.sh release armeabi-v7a,arm64-v8a,x86,x86_64
cd ../..
mv out/filament-android-release.aar out/filament-${TAG}-android.aar
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
- mv out/filamat-android-lite-release.aar out/filamat-${TAG}-lite-android.aar
mv out/gltfio-android-release.aar out/gltfio-${TAG}-android.aar
mv out/filament-utils-android-release.aar out/filament-utils-${TAG}-android.aar
- name: Sign sample-gltf-viewer
run: |
echo "${APK_KEYSTORE_BASE64}" > filament.jks.base64
- base64 --decode filament.jks.base64 > filament.jks
+ base64 --decode -i filament.jks.base64 > filament.jks
BUILD_TOOLS_VERSION=$(ls ${ANDROID_HOME}/build-tools | sort -V | tail -n 1)
APKSIGNER=${ANDROID_HOME}/build-tools/${BUILD_TOOLS_VERSION}/apksigner
IN_FILE="out/sample-gltf-viewer-release.apk"
@@ -149,7 +152,7 @@ jobs:
build-ios:
name: build-ios
- runs-on: macos-latest
+ runs-on: macos-14-xlarge
if: github.event_name == 'release' || github.event.inputs.platform == 'ios'
steps:
@@ -160,7 +163,7 @@ jobs:
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- name: Run build script
@@ -182,7 +185,7 @@ jobs:
build-windows:
name: build-windows
- runs-on: windows-2019
+ runs-on: windows-2019-32core
if: github.event_name == 'release' || github.event.inputs.platform == 'windows'
steps:
@@ -194,7 +197,7 @@ jobs:
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
shell: bash
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- name: Run build script
@@ -202,7 +205,7 @@ jobs:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
build\windows\build-github.bat release
- cd ..\..
+ echo on
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
shell: cmd
- uses: actions/github-script@v6
diff --git a/.github/workflows/verify-release-notes.yml b/.github/workflows/verify-release-notes.yml
index f4901364489..d7021cc5394 100644
--- a/.github/workflows/verify-release-notes.yml
+++ b/.github/workflows/verify-release-notes.yml
@@ -24,3 +24,4 @@ jobs:
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pull-request-number: ${{ github.event.pull_request.number }}
+ release-notes-file: 'NEW_RELEASE_NOTES.md'
diff --git a/.github/workflows/web-continuous.yml b/.github/workflows/web-continuous.yml
index ceabe618b24..b471aa9361b 100644
--- a/.github/workflows/web-continuous.yml
+++ b/.github/workflows/web-continuous.yml
@@ -10,14 +10,14 @@ on:
jobs:
build-web:
name: build-web
- runs-on: macos-latest
+ runs-on: ubuntu-22.04-16core
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/web && printf "y" | ./build.sh continuous
- - uses: actions/upload-artifact@v1.0.0
+ - uses: actions/upload-artifact@v4
with:
name: filament-web
path: out/filament-release-web.tgz
diff --git a/.github/workflows/windows-continuous.yml b/.github/workflows/windows-continuous.yml
index c2b115e577c..316fd3bc985 100644
--- a/.github/workflows/windows-continuous.yml
+++ b/.github/workflows/windows-continuous.yml
@@ -10,15 +10,15 @@ on:
jobs:
build-windows:
name: build-windows
- runs-on: windows-2019
+ runs-on: windows-2019-32core
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.6
- name: Run build script
run: |
build\windows\build-github.bat continuous
shell: cmd
- - uses: actions/upload-artifact@v1.0.0
+ - uses: actions/upload-artifact@v4
with:
name: filament-windows
path: out/filament-windows.tgz
diff --git a/.gitignore b/.gitignore
index 196b33a061f..8a48127ca4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ settings.json
test*.png
test*.json
results
+/compile_commands.json
+/.cache
diff --git a/BUILDING.md b/BUILDING.md
index a6054b074d2..167e1736ed6 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -5,7 +5,7 @@
To build Filament, you must first install the following tools:
- CMake 3.19 (or more recent)
-- clang 7.0 (or more recent)
+- clang 14.0 (or more recent)
- [ninja 1.10](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) (or more recent)
Additional dependencies may be required for your operating system. Please refer to the appropriate
@@ -13,9 +13,10 @@ section below.
To build Filament for Android you must also install the following:
-- Android Studio Arctic Fox or more recent
+- Android Studio Flamingo or more recent
- Android SDK
- Android NDK 25.1 or higher
+- Java 17
### Environment variables
@@ -39,25 +40,27 @@ inside the Filament source tree.
To trigger an incremental debug build:
-```
-$ ./build.sh debug
+```shell
+./build.sh debug
```
To trigger an incremental release build:
-```
-$ ./build.sh release
+```shell
+./build.sh release
```
To trigger both incremental debug and release builds:
-```
-$ ./build.sh debug release
+```shell
+./build.sh debug release
```
+If build fails for some reasons, it may leave the `out/` directory in a broken state. You can
+force a clean build by adding the `-c` flag in that case.
+
To install the libraries and executables in `out/debug/` and `out/release/`, add the `-i` flag.
-You can force a clean build by adding the `-c` flag. The script offers more features described
-by executing `build.sh -h`.
+The script offers more features described by executing `build.sh -h`.
### Filament-specific CMake Options
@@ -70,14 +73,13 @@ The following CMake options are boolean options specific to Filament:
- `FILAMENT_SUPPORTS_VULKAN`: Include the Vulkan backend
- `FILAMENT_INSTALL_BACKEND_TEST`: Install the backend test library so it can be consumed on iOS
- `FILAMENT_USE_EXTERNAL_GLES3`: Experimental: Compile Filament against OpenGL ES 3
-- `FILAMENT_USE_SWIFTSHADER`: Compile Filament against SwiftShader
- `FILAMENT_SKIP_SAMPLES`: Don't build sample apps
To turn an option on or off:
-```
-$ cd Warning: This is an experimental API. See {@link Engine#setPaused(boolean)} for
+ * caveats.
+ *
+ * @param paused Whether to start the rendering thread paused.
+ * @return A reference to this Builder for chaining calls.
+ */
+ public Builder paused(boolean paused) {
+ nSetBuilderPaused(mNativeBuilder, paused);
+ return this;
+ }
+
+ /**
+ * Set a feature flag value. This is the only way to set constant feature flags.
+ * @param name feature name
+ * @param value true to enable, false to disable
+ * @return A reference to this Builder for chaining calls.
+ */
+ public Builder feature(@NonNull String name, boolean value) {
+ nSetBuilderFeature(mNativeBuilder, name, value);
+ return this;
+ }
+
+ /**
+ * Creates an instance of Engine
+ *
+ * @return A newly created This is typically used after creating a lot of objects to start draining the command
+ * queue which has a limited size. Warning: This is an experimental API.
+ *
+ * @see #setPaused
+ */
+ public boolean isPaused() {
+ return nIsPaused(getNativeObject());
+ }
+
+ /**
+ * Pause or resume the rendering thread.
+ *
+ * Warning: This is an experimental API. In particular, note the following caveats.
+ *
+ *
+ * If all the material's variants are already compiled, the callback will be scheduled as
+ * soon as possible, but this might take a few dozen millisecond, corresponding to how
+ * many previous frames are enqueued in the backend. This also varies by backend. Therefore,
+ * it is recommended to only call this method once per material shortly after creation.
+ *
+ * If the same variant is scheduled for compilation multiple times, the first scheduling
+ * takes precedence; later scheduling are ignored.
+ *
+ * caveat: A consequence is that if a variant is scheduled on the low priority queue and later
+ * scheduled again on the high priority queue, the later scheduling is ignored.
+ * Therefore, the second callback could be called before the variant is compiled.
+ * However, the first callback, if specified, will trigger as expected.
+ *
+ * The callback is guaranteed to be called. If the engine is destroyed while some material
+ * variants are still compiling or in the queue, these will be discarded and the corresponding
+ * callback will be called. In that case however the Material pointer passed to the callback
+ * is guaranteed to be invalid (either because it's been destroyed by the user already, or,
+ * because it's been cleaned-up by the Engine).
+ *
+ * {@link UserVariantFilterBit#ALL} should be used with caution. Only variants that an application
+ * needs should be included in the variants argument. For example, the STE variant is only used
+ * for stereoscopic rendering. If an application is not planning to render in stereo, this bit
+ * should be turned off to avoid unnecessary material compilations.
+ * All RenderTargets must have a non-null In general Filament reserves the right to re-order renderables to allow for efficient
- * rendering. However clients can control ordering at a coarse level using priority.Engine
objects using a builder pattern.
+ */
+ public static class Builder {
+ @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+ private final BuilderFinalizer mFinalizer;
+ private final long mNativeBuilder;
+ private Config mConfig;
+
+ public Builder() {
+ mNativeBuilder = nCreateBuilder();
+ mFinalizer = new BuilderFinalizer(mNativeBuilder);
+ }
+
+ /**
+ * Sets the {@link Backend} for the Engine.
+ *
+ * @param backend Driver backend to use
+ * @return A reference to this Builder for chaining calls.
+ */
+ public Builder backend(Backend backend) {
+ nSetBuilderBackend(mNativeBuilder, backend.ordinal());
+ return this;
+ }
+
+ /**
+ * Sets a sharedContext for the Engine.
+ *
+ * @param sharedContext A platform-dependant OpenGL context used as a shared context
+ * when creating filament's internal context. On Android this parameter
+ * must be an instance of {@link android.opengl.EGLContext}.
+ * @return A reference to this Builder for chaining calls.
+ */
+ public Builder sharedContext(Object sharedContext) {
+ if (Platform.get().validateSharedContext(sharedContext)) {
+ nSetBuilderSharedContext(mNativeBuilder,
+ Platform.get().getSharedContextNativeHandle(sharedContext));
+ return this;
+ }
+ throw new IllegalArgumentException("Invalid shared context " + sharedContext);
+ }
+
+ /**
+ * Configure the Engine with custom parameters.
+ *
+ * @param config A {@link Config} object
+ * @return A reference to this Builder for chaining calls.
+ */
+ public Builder config(Config config) {
+ mConfig = config;
+ nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
+ config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
+ config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
+ config.jobSystemThreadCount, config.disableParallelShaderCompile,
+ config.stereoscopicType.ordinal(), config.stereoscopicEyeCount,
+ config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
+ config.disableHandleUseAfterFreeCheck,
+ config.preferredShaderLanguage.ordinal(),
+ config.forceGLES2Context, config.assertNativeWindowIsValid);
+ return this;
+ }
+
+ /**
+ * Sets the initial featureLevel for the Engine.
+ *
+ * @param featureLevel The feature level at which initialize Filament.
+ * @return A reference to this Builder for chaining calls.
+ */
+ public Builder featureLevel(FeatureLevel featureLevel) {
+ nSetBuilderFeatureLevel(mNativeBuilder, featureLevel.ordinal());
+ return this;
+ }
+
+ /**
+ * Sets the initial paused state of the rendering thread.
+ *
+ * Engine
, or null
if the GPU driver couldn't
+ * be initialized, for instance if it doesn't support the right version of OpenGL or
+ * OpenGL ES.
+ *
+ * @exception IllegalStateException can be thrown if there isn't enough memory to
+ * allocate the command buffer.
+ */
+ public Engine build() {
+ long nativeEngine = nBuilderBuild(mNativeBuilder);
+ if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
+ return new Engine(nativeEngine, mConfig);
+ }
+
+ private static class BuilderFinalizer {
+ private final long mNativeObject;
+
+ BuilderFinalizer(long nativeObject) {
+ mNativeObject = nativeObject;
+ }
+
+ @Override
+ public void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable t) { // Ignore
+ } finally {
+ nDestroyBuilder(mNativeObject);
+ }
+ }
+ }
+ }
+
+ /**
+ * Parameters for customizing the initialization of {@link Engine}.
+ */
+ public static class Config {
+
+ // #defines in Engine.h
+ private static final long FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB = 3;
+ private static final long FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB = 2;
+ private static final long FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB = 1;
+ private static final long FILAMENT_COMMAND_BUFFER_SIZE_IN_MB =
+ FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB * 3;
+
+ /**
+ * Size in MiB of the low-level command buffer arena.
+ *
+ * Each new command buffer is allocated from here. If this buffer is too small the program
+ * might terminate or rendering errors might occur.
+ *
+ * This is typically set to minCommandBufferSizeMB * 3, so that up to 3 frames can be
+ * batched-up at once.
+ *
+ * This value affects the application's memory usage.
+ */
+ public long commandBufferSizeMB = FILAMENT_COMMAND_BUFFER_SIZE_IN_MB;
+
+ /**
+ * Size in MiB of the per-frame data arena.
+ *
+ * This is the main arena used for allocations when preparing a frame.
+ * e.g.: Froxel data and high-level commands are allocated from this arena.
+ *
+ * If this size is too small, the program will abort on debug builds and have undefined
+ * behavior otherwise.
+ *
+ * This value affects the application's memory usage.
+ */
+ public long perRenderPassArenaSizeMB = FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB;
+
+ /**
+ * Size in MiB of the backend's handle arena.
+ *
+ * Backends will fallback to slower heap-based allocations when running out of space and
+ * log this condition.
+ *
+ * If 0, then the default value for the given platform is used
+ *
+ * This value affects the application's memory usage.
+ */
+ public long driverHandleArenaSizeMB = 0;
+
+ /**
+ * Minimum size in MiB of a low-level command buffer.
+ *
+ * This is how much space is guaranteed to be available for low-level commands when a new
+ * buffer is allocated. If this is too small, the engine might have to stall to wait for
+ * more space to become available, this situation is logged.
+ *
+ * This value does not affect the application's memory usage.
+ */
+ public long minCommandBufferSizeMB = FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB;
+
+ /**
+ * Size in MiB of the per-frame high level command buffer.
+ *
+ * This buffer is related to the number of draw calls achievable within a frame, if it is
+ * too small, the program will abort on debug builds and have undefined behavior otherwise.
+ *
+ * It is allocated from the 'per-render-pass arena' above. Make sure that at least 1 MiB is
+ * left in the per-render-pass arena when deciding the size of this buffer.
+ *
+ * This value does not affect the application's memory usage.
+ */
+ public long perFrameCommandsSizeMB = FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB;
+
+ /**
+ * Number of threads to use in Engine's JobSystem.
+ *
+ * Engine uses a utils::JobSystem to carry out paralleization of Engine workloads. This
+ * value sets the number of threads allocated for JobSystem. Configuring this value can be
+ * helpful in CPU-constrained environments where too many threads can cause contention of
+ * CPU and reduce performance.
+ *
+ * The default value is 0, which implies that the Engine will use a heuristic to determine
+ * the number of threads to use.
+ */
+ public long jobSystemThreadCount = 0;
+
+ /**
+ * Number of most-recently destroyed textures to track for use-after-free.
+ *
+ * This will cause the backend to throw an exception when a texture is freed but still bound
+ * to a SamplerGroup and used in a draw call. 0 disables completely.
+ *
+ * Currently only respected by the Metal backend.
+ */
+ public long textureUseAfterFreePoolSize = 0;
+
+ /**
+ * Set to `true` to forcibly disable parallel shader compilation in the backend.
+ * Currently only honored by the GL backend.
+ * @Deprecated use "backend.disable_parallel_shader_compile" feature flag instead
+ */
+ public boolean disableParallelShaderCompile = false;
+
+ /**
+ * The type of technique for stereoscopic rendering.
+ *
+ * This setting determines the algorithm used when stereoscopic rendering is enabled. This
+ * decision applies to the entire Engine for the lifetime of the Engine. E.g., multiple
+ * Views created from the Engine must use the same stereoscopic type.
+ *
+ * Each view can enable stereoscopic rendering via the StereoscopicOptions::enable flag.
+ *
+ * @see View#setStereoscopicOptions
+ */
+ public StereoscopicType stereoscopicType = StereoscopicType.NONE;
+
+ /**
+ * The number of eyes to render when stereoscopic rendering is enabled. Supported values are
+ * between 1 and Engine#getMaxStereoscopicEyes() (inclusive).
+ *
+ * @see View#setStereoscopicOptions
+ * @see Engine#getMaxStereoscopicEyes
+ */
+ public long stereoscopicEyeCount = 2;
+
+ /**
+ * @Deprecated This value is no longer used.
+ */
+ public long resourceAllocatorCacheSizeMB = 64;
+
+ /**
+ * This value determines how many frames texture entries are kept for in the cache. This
+ * is a soft limit, meaning some texture older than this are allowed to stay in the cache.
+ * Typically only one texture is evicted per frame.
+ * The default is 1.
+ */
+ public long resourceAllocatorCacheMaxAge = 1;
+
+ /**
+ * Disable backend handles use-after-free checks.
+ * @Deprecated use "backend.disable_handle_use_after_free_check" feature flag instead
+ */
+ public boolean disableHandleUseAfterFreeCheck = false;
+
+ /**
+ * Sets a preferred shader language for Filament to use.
+ *
+ * The Metal backend supports two shader languages: MSL (Metal Shading Language) and
+ * METAL_LIBRARY (precompiled .metallib). This option controls which shader language is
+ * used when materials contain both.
+ *
+ * By default, when preferredShaderLanguage is unset, Filament will prefer METAL_LIBRARY
+ * shaders if present within a material, falling back to MSL. Setting
+ * preferredShaderLanguage to ShaderLanguage::MSL will instead instruct Filament to check
+ * for the presence of MSL in a material first, falling back to METAL_LIBRARY if MSL is not
+ * present.
+ *
+ * When using a non-Metal backend, setting this has no effect.
+ */
+ public enum ShaderLanguage {
+ DEFAULT,
+ MSL,
+ METAL_LIBRARY,
+ };
+ public ShaderLanguage preferredShaderLanguage = ShaderLanguage.DEFAULT;
+
+ /**
+ * When the OpenGL ES backend is used, setting this value to true will force a GLES2.0
+ * context if supported by the Platform, or if not, will have the backend pretend
+ * it's a GLES2 context. Ignored on other backends.
+ */
+ public boolean forceGLES2Context = false;
+
+ /**
+ * Assert the native window associated to a SwapChain is valid when calling makeCurrent().
+ * This is only supported for:
+ * - PlatformEGLAndroid
+ * @Deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
+ */
+ public boolean assertNativeWindowIsValid = false;
+ }
+
+ private Engine(long nativeEngine, Config config) {
mNativeObject = nativeEngine;
mTransformManager = new TransformManager(nGetTransformManager(nativeEngine));
mLightManager = new LightManager(nGetLightManager(nativeEngine));
mRenderableManager = new RenderableManager(nGetRenderableManager(nativeEngine));
mEntityManager = new EntityManager(nGetEntityManager(nativeEngine));
+ mConfig = config;
}
/**
@@ -177,9 +511,7 @@ private Engine(long nativeEngine) {
*/
@NonNull
public static Engine create() {
- long nativeEngine = nCreateEngine(0, 0);
- if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
- return new Engine(nativeEngine);
+ return new Builder().build();
}
/**
@@ -199,9 +531,9 @@ public static Engine create() {
*/
@NonNull
public static Engine create(@NonNull Backend backend) {
- long nativeEngine = nCreateEngine(backend.ordinal(), 0);
- if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
- return new Engine(nativeEngine);
+ return new Builder()
+ .backend(backend)
+ .build();
}
/**
@@ -223,13 +555,9 @@ public static Engine create(@NonNull Backend backend) {
*/
@NonNull
public static Engine create(@NonNull Object sharedContext) {
- if (Platform.get().validateSharedContext(sharedContext)) {
- long nativeEngine = nCreateEngine(0,
- Platform.get().getSharedContextNativeHandle(sharedContext));
- if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
- return new Engine(nativeEngine);
- }
- throw new IllegalArgumentException("Invalid shared context " + sharedContext);
+ return new Builder()
+ .sharedContext(sharedContext)
+ .build();
}
/**
@@ -296,17 +624,23 @@ public FeatureLevel getSupportedFeatureLevel() {
}
/**
- * Activate all features of a given feature level. By default FeatureLevel::FEATURE_LEVEL_1 is
- * active. The selected feature level must not be higher than the value returned by
- * getActiveFeatureLevel() and it's not possible lower the active feature level.
+ * Activate all features of a given feature level. If an explicit feature level is not specified
+ * at Engine initialization time via {@link Builder#featureLevel}, the default feature level is
+ * {@link FeatureLevel#FEATURE_LEVEL_0} on devices not compatible with GLES 3.0; otherwise, the
+ * default is {@link FeatureLevel::FEATURE_LEVEL_1}. The selected feature level must not be
+ * higher than the value returned by {@link #getActiveFeatureLevel} and it's not possible lower
+ * the active feature level. Additionally, it is not possible to modify the feature level at all
+ * if the Engine was initialized at {@link FeatureLevel#FEATURE_LEVEL_0}.
*
- * @param featureLevel the feature level to activate. If featureLevel is lower than
- * getActiveFeatureLevel(), the current (higher) feature level is kept.
- * If featureLevel is higher than getSupportedFeatureLevel(), an exception
- * is thrown, or the program is terminated if exceptions are disabled.
+ * @param featureLevel the feature level to activate. If featureLevel is lower than {@link
+ * #getActiveFeatureLevel}, the current (higher) feature level is kept. If
+ * featureLevel is higher than {@link #getSupportedFeatureLevel}, or if the
+ * engine was initialized at feature level 0, an exception is thrown, or the
+ * program is terminated if exceptions are disabled.
*
* @return the active feature level.
*
+ * @see Builder#featureLevel
* @see #getSupportedFeatureLevel
* @see #getActiveFeatureLevel
*/
@@ -352,6 +686,37 @@ public boolean isAutomaticInstancingEnabled() {
return nIsAutomaticInstancingEnabled(getNativeObject());
}
+ /**
+ * Retrieves the configuration settings of this {@link Engine}.
+ *
+ * This method returns the configuration object that was supplied to the Engine's {@link
+ * Builder#config} method during the creation of this Engine. If the {@link Builder::config}
+ * method was not explicitly called (or called with null), this method returns the default
+ * configuration settings.
+ *
+ * @return a {@link Config} object with this Engine's configuration
+ * @see Builder#config
+ */
+ @NonNull
+ public Config getConfig() {
+ if (mConfig == null) {
+ mConfig = new Config();
+ }
+ return mConfig;
+ }
+
+ /**
+ * Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of
+ * eyes rendered is set at Engine creation time with the {@link Config#stereoscopicEyeCount}
+ * setting.
+ *
+ * @return the max number of stereoscopic eyes supported
+ * @see Config#stereoscopicEyeCount
+ */
+ public long getMaxStereoscopicEyes() {
+ return nGetMaxStereoscopicEyes(getNativeObject());
+ }
+
// SwapChain
@@ -366,7 +731,7 @@ public boolean isAutomaticInstancingEnabled() {
*/
@NonNull
public SwapChain createSwapChain(@NonNull Object surface) {
- return createSwapChain(surface, SwapChain.CONFIG_DEFAULT);
+ return createSwapChain(surface, SwapChainFlags.CONFIG_DEFAULT);
}
/**
@@ -374,15 +739,15 @@ public SwapChain createSwapChain(@NonNull Object surface) {
*
* @param surface on Android, must be an instance of {@link android.view.Surface}
*
- * @param flags configuration flags, see {@link SwapChain}
+ * @param flags configuration flags, see {@link SwapChainFlags}
*
* @return a newly created {@link SwapChain} object
*
* @exception IllegalStateException can be thrown if the SwapChain couldn't be created
*
- * @see SwapChain#CONFIG_DEFAULT
- * @see SwapChain#CONFIG_TRANSPARENT
- * @see SwapChain#CONFIG_READABLE
+ * @see SwapChainFlags#CONFIG_DEFAULT
+ * @see SwapChainFlags#CONFIG_TRANSPARENT
+ * @see SwapChainFlags#CONFIG_READABLE
*
*/
@NonNull
@@ -400,21 +765,22 @@ public SwapChain createSwapChain(@NonNull Object surface, long flags) {
*
* @param width width of the rendering buffer
* @param height height of the rendering buffer
- * @param flags configuration flags, see {@link SwapChain}
+ * @param flags configuration flags, see {@link SwapChainFlags}
*
* @return a newly created {@link SwapChain} object
*
* @exception IllegalStateException can be thrown if the SwapChain couldn't be created
*
- * @see SwapChain#CONFIG_DEFAULT
- * @see SwapChain#CONFIG_TRANSPARENT
- * @see SwapChain#CONFIG_READABLE
+ * @see SwapChainFlags#CONFIG_DEFAULT
+ * @see SwapChainFlags#CONFIG_TRANSPARENT
+ * @see SwapChainFlags#CONFIG_READABLE
*
*/
@NonNull
public SwapChain createSwapChain(int width, int height, long flags) {
if (width >= 0 && height >= 0) {
- long nativeSwapChain = nCreateSwapChainHeadless(getNativeObject(), width, height, flags);
+ long nativeSwapChain =
+ nCreateSwapChainHeadless(getNativeObject(), width, height, flags);
if (nativeSwapChain == 0) throw new IllegalStateException("Couldn't create SwapChain");
return new SwapChain(nativeSwapChain, null);
}
@@ -426,11 +792,12 @@ public SwapChain createSwapChain(int width, int height, long flags) {
*
* @param surface a properly initialized {@link NativeSurface}
*
- * @param flags configuration flags, see {@link SwapChain}
+ * @param flags configuration flags, see {@link SwapChainFlags}
*
* @return a newly created {@link SwapChain} object
*
- * @exception IllegalStateException can be thrown if the {@link SwapChain} couldn't be created
+ * @exception IllegalStateException can be thrown if the {@link SwapChainFlags} couldn't be
+ * created
*/
@NonNull
public SwapChain createSwapChainFromNativeSurface(@NonNull NativeSurface surface, long flags) {
@@ -449,6 +816,160 @@ public void destroySwapChain(@NonNull SwapChain swapChain) {
swapChain.clearNativeObject();
}
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidRenderer(@NonNull Renderer object) {
+ return nIsValidRenderer(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidView(@NonNull View object) {
+ return nIsValidView(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidScene(@NonNull Scene object) {
+ return nIsValidScene(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidFence(@NonNull Fence object) {
+ return nIsValidFence(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidStream(@NonNull Stream object) {
+ return nIsValidStream(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidIndexBuffer(@NonNull IndexBuffer object) {
+ return nIsValidIndexBuffer(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidVertexBuffer(@NonNull VertexBuffer object) {
+ return nIsValidVertexBuffer(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidSkinningBuffer(@NonNull SkinningBuffer object) {
+ return nIsValidSkinningBuffer(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidIndirectLight(@NonNull IndirectLight object) {
+ return nIsValidIndirectLight(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidMaterial(@NonNull Material object) {
+ return nIsValidMaterial(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param ma Material
+ * @param mi MaterialInstance to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidMaterialInstance(@NonNull Material ma, MaterialInstance mi) {
+ return nIsValidMaterialInstance(getNativeObject(), ma.getNativeObject(), mi.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidExpensiveMaterialInstance(@NonNull MaterialInstance object) {
+ return nIsValidExpensiveMaterialInstance(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidSkybox(@NonNull Skybox object) {
+ return nIsValidSkybox(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidColorGrading(@NonNull ColorGrading object) {
+ return nIsValidColorGrading(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidTexture(@NonNull Texture object) {
+ return nIsValidTexture(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidRenderTarget(@NonNull RenderTarget object) {
+ return nIsValidRenderTarget(getNativeObject(), object.getNativeObject());
+ }
+
+ /**
+ * Returns whether the object is valid.
+ * @param object Object to check for validity
+ * @return returns true if the specified object is valid.
+ */
+ public boolean isValidSwapChain(@NonNull SwapChain object) {
+ return nIsValidSwapChain(getNativeObject(), object.getNativeObject());
+ }
+
// View
/**
@@ -753,6 +1274,96 @@ public void flushAndWait() {
nFlushAndWait(getNativeObject());
}
+ /**
+ * Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) but does not wait
+ * for commands to be either executed or the hardware finished.
+ *
+ *
+ */
+ public void setPaused(boolean paused) {
+ nSetPaused(getNativeObject(), paused);
+ }
+
+ /**
+ * Switch the command queue to unprotected mode. Protected mode can be activated via
+ * Renderer::beginFrame() using a protected SwapChain.
+ * @see Renderer
+ * @see SwapChain
+ */
+ public void unprotected() {
+ nUnprotected(getNativeObject());
+ }
+
+ /**
+ * Get the current time. This is a convenience function that simply returns the
+ * time in nanosecond since epoch of std::chrono::steady_clock.
+ * @return current time in nanosecond since epoch of std::chrono::steady_clock.
+ * @see Renderer#beginFrame
+ */
+ public static native long getSteadyClockTimeNano();
+
+
+ /**
+ * Checks if a feature flag exists
+ * @param name name of the feature flag to check
+ * @return true if it exists false otherwise
+ */
+ public boolean hasFeatureFlag(@NonNull String name) {
+ return nHasFeatureFlag(mNativeObject, name);
+ }
+
+ /**
+ * Set the value of a non-constant feature flag.
+ * @param name name of the feature flag to set
+ * @param value value to set
+ * @return true if the value was set, false if the feature flag is constant or doesn't exist.
+ */
+ public boolean setFeatureFlag(@NonNull String name, boolean value) {
+ return nSetFeatureFlag(mNativeObject, name, value);
+ }
+
+ /**
+ * Retrieves the value of any feature flag.
+ * @param name name of the feature flag
+ * @return the value of the flag if it exists
+ * @exception IllegalArgumentException is thrown if the feature flag doesn't exist
+ */
+ public boolean getFeatureFlag(@NonNull String name) {
+ if (!hasFeatureFlag(name)) {
+ throw new IllegalArgumentException("The feature flag \"" + name + "\" doesn't exist");
+ }
+ return nGetFeatureFlag(mNativeObject, name);
+ }
+
@UsedByReflection("TextureHelper.java")
public long getNativeObject() {
if (mNativeObject == 0) {
@@ -779,23 +1390,22 @@ private static void assertDestroy(boolean success) {
}
}
- private static native long nCreateEngine(long backend, long sharedContext);
private static native void nDestroyEngine(long nativeEngine);
private static native long nGetBackend(long nativeEngine);
private static native long nCreateSwapChain(long nativeEngine, Object nativeWindow, long flags);
private static native long nCreateSwapChainHeadless(long nativeEngine, int width, int height, long flags);
private static native long nCreateSwapChainFromRawPointer(long nativeEngine, long pointer, long flags);
- private static native boolean nDestroySwapChain(long nativeEngine, long nativeSwapChain);
private static native long nCreateView(long nativeEngine);
- private static native boolean nDestroyView(long nativeEngine, long nativeView);
private static native long nCreateRenderer(long nativeEngine);
- private static native boolean nDestroyRenderer(long nativeEngine, long nativeRenderer);
private static native long nCreateCamera(long nativeEngine, int entity);
private static native long nGetCameraComponent(long nativeEngine, int entity);
private static native void nDestroyCameraComponent(long nativeEngine, int entity);
private static native long nCreateScene(long nativeEngine);
- private static native boolean nDestroyScene(long nativeEngine, long nativeScene);
private static native long nCreateFence(long nativeEngine);
+
+ private static native boolean nDestroyRenderer(long nativeEngine, long nativeRenderer);
+ private static native boolean nDestroyView(long nativeEngine, long nativeView);
+ private static native boolean nDestroyScene(long nativeEngine, long nativeScene);
private static native boolean nDestroyFence(long nativeEngine, long nativeFence);
private static native boolean nDestroyStream(long nativeEngine, long nativeStream);
private static native boolean nDestroyIndexBuffer(long nativeEngine, long nativeIndexBuffer);
@@ -808,8 +1418,30 @@ private static void assertDestroy(boolean success) {
private static native boolean nDestroyColorGrading(long nativeEngine, long nativeColorGrading);
private static native boolean nDestroyTexture(long nativeEngine, long nativeTexture);
private static native boolean nDestroyRenderTarget(long nativeEngine, long nativeTarget);
+ private static native boolean nDestroySwapChain(long nativeEngine, long nativeSwapChain);
+ private static native boolean nIsValidRenderer(long nativeEngine, long nativeRenderer);
+ private static native boolean nIsValidView(long nativeEngine, long nativeView);
+ private static native boolean nIsValidScene(long nativeEngine, long nativeScene);
+ private static native boolean nIsValidFence(long nativeEngine, long nativeFence);
+ private static native boolean nIsValidStream(long nativeEngine, long nativeStream);
+ private static native boolean nIsValidIndexBuffer(long nativeEngine, long nativeIndexBuffer);
+ private static native boolean nIsValidVertexBuffer(long nativeEngine, long nativeVertexBuffer);
+ private static native boolean nIsValidSkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
+ private static native boolean nIsValidIndirectLight(long nativeEngine, long nativeIndirectLight);
+ private static native boolean nIsValidMaterial(long nativeEngine, long nativeMaterial);
+ private static native boolean nIsValidMaterialInstance(long nativeEngine, long nativeMaterial, long nativeMaterialInstance);
+ private static native boolean nIsValidExpensiveMaterialInstance(long nativeEngine, long nativeMaterialInstance);
+ private static native boolean nIsValidSkybox(long nativeEngine, long nativeSkybox);
+ private static native boolean nIsValidColorGrading(long nativeEngine, long nativeColorGrading);
+ private static native boolean nIsValidTexture(long nativeEngine, long nativeTexture);
+ private static native boolean nIsValidRenderTarget(long nativeEngine, long nativeTarget);
+ private static native boolean nIsValidSwapChain(long nativeEngine, long nativeSwapChain);
private static native void nDestroyEntity(long nativeEngine, int entity);
private static native void nFlushAndWait(long nativeEngine);
+ private static native void nFlush(long nativeEngine);
+ private static native boolean nIsPaused(long nativeEngine);
+ private static native void nSetPaused(long nativeEngine, boolean paused);
+ private static native void nUnprotected(long nativeEngine);
private static native long nGetTransformManager(long nativeEngine);
private static native long nGetLightManager(long nativeEngine);
private static native long nGetRenderableManager(long nativeEngine);
@@ -817,7 +1449,28 @@ private static void assertDestroy(boolean success) {
private static native long nGetEntityManager(long nativeEngine);
private static native void nSetAutomaticInstancingEnabled(long nativeEngine, boolean enable);
private static native boolean nIsAutomaticInstancingEnabled(long nativeEngine);
+ private static native long nGetMaxStereoscopicEyes(long nativeEngine);
private static native int nGetSupportedFeatureLevel(long nativeEngine);
private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
private static native int nGetActiveFeatureLevel(long nativeEngine);
+ private static native boolean nHasFeatureFlag(long nativeEngine, String name);
+ private static native boolean nSetFeatureFlag(long nativeEngine, String name, boolean value);
+ private static native boolean nGetFeatureFlag(long nativeEngine, String name);
+
+ private static native long nCreateBuilder();
+ private static native void nDestroyBuilder(long nativeBuilder);
+ private static native void nSetBuilderBackend(long nativeBuilder, long backend);
+ private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
+ long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
+ long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount,
+ boolean disableParallelShaderCompile, int stereoscopicType, long stereoscopicEyeCount,
+ long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
+ boolean disableHandleUseAfterFreeCheck,
+ int preferredShaderLanguage,
+ boolean forceGLES2Context, boolean assertNativeWindowIsValid);
+ private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
+ private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
+ private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
+ private static native void nSetBuilderFeature(long nativeBuilder, String name, boolean value);
+ private static native long nBuilderBuild(long nativeBuilder);
}
diff --git a/android/filament-android/src/main/java/com/google/android/filament/LightManager.java b/android/filament-android/src/main/java/com/google/android/filament/LightManager.java
index 1c0ce0b0978..bc930d7c6cf 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/LightManager.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/LightManager.java
@@ -258,6 +258,7 @@ public static class ShadowOptions {
* shadows that are too far and wouldn't contribute to the scene much, improving
* performance and quality. This value is always positive.
* Use 0.0f to use the camera far distance.
+ * This only affect directional lights.
*/
public float shadowFar = 0.0f;
@@ -368,6 +369,17 @@ public static class ShadowOptions {
* enabled. (2cm by default).
*/
public float shadowBulbRadius = 0.02f;
+
+ /**
+ * Transforms the shadow direction. Must be a unit quaternion.
+ * The default is identity.
+ * Ignored if the light type isn't directional. For artistic use. Use with caution.
+ * The quaternion is stored as the imaginary part in the first 3 elements and the real
+ * part in the last element of the transform array.
+ */
+ @NonNull
+ @Size(min = 4, max = 4)
+ public float[] transform = { 0.0f, 0.0f, 0.0f, 1.0f };
}
public static class ShadowCascades {
@@ -506,7 +518,7 @@ public Builder shadowOptions(@NonNull ShadowOptions options) {
options.polygonOffsetConstant, options.polygonOffsetSlope,
options.screenSpaceContactShadows,
options.stepCount, options.maxShadowDistance,
- options.elvsm, options.blurWidth, options.shadowBulbRadius);
+ options.elvsm, options.blurWidth, options.shadowBulbRadius, options.transform);
return this;
}
@@ -1169,7 +1181,7 @@ private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize
boolean stable, boolean lispsm,
float polygonOffsetConstant, float polygonOffsetSlope,
boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance,
- boolean elvsm, float blurWidth, float shadowBulbRadius);
+ boolean elvsm, float blurWidth, float shadowBulbRadius, float[] transform);
private static native void nBuilderCastLight(long nativeBuilder, boolean enabled);
private static native void nBuilderPosition(long nativeBuilder, float x, float y, float z);
private static native void nBuilderDirection(long nativeBuilder, float x, float y, float z);
diff --git a/android/filament-android/src/main/java/com/google/android/filament/Material.java b/android/filament-android/src/main/java/com/google/android/filament/Material.java
index 54aeca69e71..b304f8a6ab7 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/Material.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/Material.java
@@ -18,9 +18,11 @@
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.Size;
import com.google.android.filament.proguard.UsedByNative;
+import com.google.android.filament.Engine.FeatureLevel;
import java.nio.Buffer;
import java.util.ArrayList;
@@ -46,6 +48,8 @@ private EnumCache() { }
static final BlendingMode[] sBlendingModeValues = BlendingMode.values();
static final RefractionMode[] sRefractionModeValues = RefractionMode.values();
static final RefractionType[] sRefractionTypeValues = RefractionType.values();
+ static final ReflectionMode[] sReflectionModeValues = ReflectionMode.values();
+ static final FeatureLevel[] sFeatureLevelValues = FeatureLevel.values();
static final VertexDomain[] sVertexDomainValues = VertexDomain.values();
static final CullingMode[] sCullingModeValues = CullingMode.values();
static final VertexBuffer.VertexAttribute[] sVertexAttributeValues =
@@ -181,6 +185,18 @@ public enum RefractionType {
THIN
}
+ /**
+ * Supported reflection modes
+ *
+ * @see
+ *
+ * Lighting: reflections
+ */
+ public enum ReflectionMode {
+ DEFAULT,
+ SCREEN_SPACE
+ }
+
/**
* Supported types of vertex domains
*
@@ -223,6 +239,31 @@ public enum CullingMode {
FRONT_AND_BACK
}
+ public enum CompilerPriorityQueue {
+ HIGH,
+ LOW
+ }
+
+ public static class UserVariantFilterBit {
+ /** Directional lighting */
+ public static int DIRECTIONAL_LIGHTING = 0x01;
+ /** Dynamic lighting */
+ public static int DYNAMIC_LIGHTING = 0x02;
+ /** Shadow receiver */
+ public static int SHADOW_RECEIVER = 0x04;
+ /** Skinning */
+ public static int SKINNING = 0x08;
+ /** Fog */
+ public static int FOG = 0x10;
+ /** Variance shadow maps */
+ public static int VSM = 0x20;
+ /** Screen-space reflections */
+ public static int SSR = 0x40;
+ /** Instanced stereo rendering */
+ public static int STE = 0x80;
+ public static int ALL = 0xFF;
+ }
+
@UsedByNative("Material.cpp")
public static class Parameter {
private static final Type[] sTypeValues = Type.values();
@@ -303,8 +344,18 @@ public Material(long nativeMaterial) {
}
public static class Builder {
+ public enum ShadowSamplingQuality {
+ /** 2x2 PCF */
+ HARD,
+ /** 3x3 gaussian filter */
+ LOW,
+ }
+
private Buffer mBuffer;
private int mSize;
+ private int mShBandCount = 0;
+ private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW;
+
/**
* Specifies the material data. The material data is a binary blob produced by
@@ -320,6 +371,34 @@ public Builder payload(@NonNull Buffer buffer, @IntRange(from = 0) int size) {
return this;
}
+ /**
+ * Sets the quality of the indirect lights computations. This is only taken into account
+ * if this material is lit and in the surface domain. This setting will affect the
+ * IndirectLight computation if one is specified on the Scene and Spherical Harmonics
+ * are used for the irradiance.
+ *
+ * @param shBandCount Number of spherical harmonic bands. Must be 1, 2 or 3 (default).
+ * @return Reference to this Builder for chaining calls.
+ * @see IndirectLight
+ */
+ @NonNull
+ public Builder sphericalHarmonicsBandCount(@IntRange(from = 0) int shBandCount) {
+ mShBandCount = shBandCount;
+ return this;
+ }
+
+ /**
+ * Set the quality of shadow sampling. This is only taken into account
+ * if this material is lit and in the surface domain.
+ * @param quality
+ * @return Reference to this Builder for chaining calls.
+ */
+ @NonNull
+ public Builder shadowSamplingQuality(ShadowSamplingQuality quality) {
+ mShadowSamplingQuality = quality;
+ return this;
+ }
+
/**
* Creates and returns the Material object.
*
@@ -331,12 +410,62 @@ public Builder payload(@NonNull Buffer buffer, @IntRange(from = 0) int size) {
*/
@NonNull
public Material build(@NonNull Engine engine) {
- long nativeMaterial = nBuilderBuild(engine.getNativeObject(), mBuffer, mSize);
+ long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
+ mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal());
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
return new Material(nativeMaterial);
}
}
+
+ /**
+ * Asynchronously ensures that a subset of this Material's variants are compiled. After issuing
+ * several compile() calls in a row, it is recommended to call {@link Engine#flush}
+ * such that the backend can start the compilation work as soon as possible.
+ * The provided callback is guaranteed to be called on the main thread after all specified
+ * variants of the material are compiled. This can take hundreds of milliseconds.
+ *COLOR
attachment.
For example, this could be used to draw a semitransparent HUD, if a client wishes to * avoid using a separate View for the HUD. Note that priority is completely orthogonal to * {@link Builder#layerMask}, which merely controls visibility.
+ + *The Skybox always using the lowest priority, so it's drawn last, which may improve + * performance.
* *The priority is clamped to the range [0..7], defaults to 4; 7 is lowest priority * (rendered last).
* * @see Builder#blendOrder */ + + /** + * Provides coarse-grained control over draw order. + * + *In general Filament reserves the right to re-order renderables to allow for efficient + * rendering. However clients can control ordering at a coarse level using priority. + * The priority is applied separately for opaque and translucent objects, that is, opaque + * objects are always drawn before translucent objects regardless of the priority.
+ * + *For example, this could be used to draw a semitransparent HUD, if a client wishes to + * avoid using a separate View for the HUD. Note that priority is completely orthogonal to + * {@link Builder#layerMask}, which merely controls visibility.
+ + *The Skybox always using the lowest priority, so it's drawn last, which may improve + * performance.
+ * + * @param priority clamped to the range [0..7], defaults to 4; 7 is lowest priority + * (rendered last). + * + * @return Builder reference for chaining calls. + * + * @see Builder#channel + * @see Builder#blendOrder + * @see #setPriority + * @see #setBlendOrderAt + */ @NonNull public Builder priority(@IntRange(from = 0, to = 7) int priority) { nBuilderPriority(mNativeBuilder, priority); return this; } + /** + * Set the channel this renderable is associated to. There can be 4 channels. + * + *All renderables in a given channel are rendered together, regardless of anything else. + * They are sorted as usual within a channel.
+ *Channels work similarly to priorities, except that they enforce the strongest + * ordering.
+ * + *Channels 0 and 1 may not have render primitives using a material with `refractionType` + * set to `screenspace`.
+ * + * @param channel clamped to the range [0..3], defaults to 2. + * + * @return Builder reference for chaining calls. + * + * @see Builder::blendOrder() + * @see Builder::priority() + * @see RenderableManager::setBlendOrderAt() + */ + @NonNull + public Builder channel(@IntRange(from = 0, to = 3) int channel) { + nBuilderChannel(mNativeBuilder, channel); + return this; + } + /** * Controls frustum culling, true by default. * @@ -300,16 +382,16 @@ public Builder lightChannel(@IntRange(from = 0, to = 7) int channel, boolean ena /** * Specifies the number of draw instance of this renderable. The default is 1 instance and - * the maximum number of instances allowed is 65535. 0 is invalid. + * the maximum number of instances allowed is 32767. 0 is invalid. * All instances are culled using the same bounding box, so care must be taken to make * sure all instances render inside the specified bounding box. * The material can use getInstanceIndex() in the vertex shader to get the instance index and * possibly adjust the position or transform. * - * @param instanceCount the number of instances silently clamped between 1 and 65535. + * @param instanceCount the number of instances silently clamped between 1 and 32767. */ @NonNull - public Builder instances(@IntRange(from = 1, to = 65535) int instanceCount) { + public Builder instances(@IntRange(from = 1, to = 32767) int instanceCount) { nBuilderInstances(mNativeBuilder, instanceCount); return this; } @@ -361,7 +443,19 @@ public Builder screenSpaceContactShadows(boolean enabled) { */ @NonNull public Builder enableSkinningBuffers(boolean enabled) { - nEnableSkinningBuffers(mNativeBuilder, enabled); + nBuilderEnableSkinningBuffers(mNativeBuilder, enabled); + return this; + } + + /** + * Controls if this renderable is affected by the large-scale fog. + * @param enabled If true, enables large-scale fog on this object. Disables it otherwise. + * True by default. + * @return thisBuilder
object for chaining calls
+ */
+ @NonNull
+ public Builder fog(boolean enabled) {
+ nBuilderFog(mNativeBuilder, enabled);
return this;
}
@@ -430,14 +524,7 @@ public Builder skinning(@IntRange(from = 0, to = 255) int boneCount, @NonNull Bu
}
/**
- * Controls if the renderable has vertex morphing targets, zero by default. This is
- * required to enable GPU morphing.
- *
- * Filament supports two morphing modes: standard (default) and legacy.
- * - *For standard morphing, A {@link MorphTargetBuffer} must be created and provided via
- * {@link RenderableManager#setMorphTargetBufferAt}. Standard morphing supports up to
- * CONFIG_MAX_MORPH_TARGET_COUNT
morph targets.
MORPH_POSITION_0
etc).
@@ -455,6 +542,22 @@ public Builder morphing(@IntRange(from = 0, to = 255) int targetCount) {
return this;
}
+ /**
+ * Controls if the renderable has vertex morphing targets, zero by default.
+ *
+ * For standard morphing, A {@link MorphTargetBuffer} must be provided.
+ * Standard morphing supports up to
+ * CONFIG_MAX_MORPH_TARGET_COUNT
morph targets.
See also {@link RenderableManager#setMorphWeights}, which can be called on a per-frame basis + * to advance the animation.
+ */ + @NonNull + public Builder morphing(@NonNull MorphTargetBuffer morphTargetBuffer) { + nBuilderMorphingStandard(mNativeBuilder, morphTargetBuffer.getNativeObject()); + return this; + } + /** * Specifies the morph target buffer for a primitive. * @@ -466,31 +569,13 @@ public Builder morphing(@IntRange(from = 0, to = 255) int targetCount) { * * @param level the level of detail (lod), only 0 can be specified * @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor - * @param morphTargetBuffer specifies the morph target buffer * @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices) - * @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3) */ @NonNull public Builder morphing(@IntRange(from = 0) int level, @IntRange(from = 0) int primitiveIndex, - @NonNull MorphTargetBuffer morphTargetBuffer, - @IntRange(from = 0) int offset, - @IntRange(from = 0) int count) { - nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex, - morphTargetBuffer.getNativeObject(), offset, count); - return this; - } - - /** - * Utility method to specify morph target buffer for a primitive. - * For details, see the {@link RenderableManager.Builder#morphing}. - */ - @NonNull - public Builder morphing(@IntRange(from = 0) int level, - @IntRange(from = 0) int primitiveIndex, - @NonNull MorphTargetBuffer morphTargetBuffer) { - nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex, - morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount()); + @IntRange(from = 0) int offset) { + nBuilderSetMorphTargetBufferOffsetAt(mNativeBuilder, level, primitiveIndex, offset); return this; } @@ -593,26 +678,11 @@ public void setMorphWeights(@EntityInstance int i, @NonNull float[] weights, @In * * @see Builder#morphing */ - public void setMorphTargetBufferAt(@EntityInstance int i, - @IntRange(from = 0) int level, - @IntRange(from = 0) int primitiveIndex, - @NonNull MorphTargetBuffer morphTargetBuffer, - @IntRange(from = 0) int offset, - @IntRange(from = 0) int count) { - nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, - morphTargetBuffer.getNativeObject(), offset, count); - } - - /** - * Utility method to change morph target buffer for the given primitive. - * For details, see the {@link RenderableManager#setMorphTargetBufferAt}. - */ - public void setMorphTargetBufferAt(@EntityInstance int i, + public void setMorphTargetBufferOffsetAt(@EntityInstance int i, @IntRange(from = 0) int level, @IntRange(from = 0) int primitiveIndex, - @NonNull MorphTargetBuffer morphTargetBuffer) { - nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, - morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount()); + @IntRange(from = 0) int offset) { + nSetMorphTargetBufferOffsetAt(mNativeObject, i, level, primitiveIndex, 0, offset); } /** @@ -655,6 +725,15 @@ public void setPriority(@EntityInstance int i, @IntRange(from = 0, to = 7) int p nSetPriority(mNativeObject, i, priority); } + /** + * Changes the channel of a renderable + * + * @see Builder#channel + */ + public void setChannel(@EntityInstance int i, @IntRange(from = 0, to = 3) int channel) { + nSetChannel(mNativeObject, i, channel); + } + /** * Changes whether or not frustum culling is on. * @@ -664,6 +743,23 @@ public void setCulling(@EntityInstance int i, boolean enabled) { nSetCulling(mNativeObject, i, enabled); } + /** + * Changes whether or not the large-scale fog is applied to this renderable + * @see Builder#fog + */ + public void setFogEnabled(@EntityInstance int i, boolean enabled) { + nSetFogEnabled(mNativeObject, i, enabled); + } + + /** + * Returns whether large-scale fog is enabled for this renderable. + * @return True if fog is enabled for this renderable. + * @see Builder#fog + */ + public boolean getFogEnabled(@EntityInstance int i) { + return nGetFogEnabled(mNativeObject, i); + } + /** * Enables or disables a light channel. * Light channel 0 is enabled by default. @@ -773,6 +869,13 @@ public void setMaterialInstanceAt(@EntityInstance int i, @IntRange(from = 0) int nSetMaterialInstanceAt(mNativeObject, i, primitiveIndex, materialInstance.getNativeObject()); } + /** + * Clears the material instance for the given primitive. + */ + public void clearMaterialInstanceAt(@EntityInstance int i, @IntRange(from = 0) int primitiveIndex) { + nClearMaterialInstanceAt(mNativeObject, i, primitiveIndex); + } + /** * Creates a MaterialInstance Java wrapper object for a particular material instance. */ @@ -870,12 +973,14 @@ public long getNativeObject() { private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer); private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int count); private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int minIndex, int maxIndex, int count); + private static native void nBuilderGeometryType(long nativeBuilder, int type); private static native void nBuilderMaterial(long nativeBuilder, int index, long nativeMaterialInstance); private static native void nBuilderBlendOrder(long nativeBuilder, int index, int blendOrder); private static native void nBuilderGlobalBlendOrderEnabled(long nativeBuilder, int index, boolean enabled); private static native void nBuilderBoundingBox(long nativeBuilder, float cx, float cy, float cz, float ex, float ey, float ez); private static native void nBuilderLayerMask(long nativeBuilder, int select, int value); private static native void nBuilderPriority(long nativeBuilder, int priority); + private static native void nBuilderChannel(long nativeBuilder, int channel); private static native void nBuilderCulling(long nativeBuilder, boolean enabled); private static native void nBuilderCastShadows(long nativeBuilder, boolean enabled); private static native void nBuilderReceiveShadows(long nativeBuilder, boolean enabled); @@ -884,8 +989,10 @@ public long getNativeObject() { private static native int nBuilderSkinningBones(long nativeBuilder, int boneCount, Buffer bones, int remaining); private static native void nBuilderSkinningBuffer(long nativeBuilder, long nativeSkinningBuffer, int boneCount, int offset); private static native void nBuilderMorphing(long nativeBuilder, int targetCount); - private static native void nBuilderSetMorphTargetBufferAt(long nativeBuilder, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count); - private static native void nEnableSkinningBuffers(long nativeBuilder, boolean enabled); + private static native void nBuilderMorphingStandard(long nativeBuilder, long nativeMorphTargetBuffer); + private static native void nBuilderSetMorphTargetBufferOffsetAt(long nativeBuilder, int level, int primitiveIndex, int offset); + private static native void nBuilderEnableSkinningBuffers(long nativeBuilder, boolean enabled); + private static native void nBuilderFog(long nativeBuilder, boolean enabled); private static native void nBuilderLightChannel(long nativeRenderableManager, int channel, boolean enable); private static native void nBuilderInstances(long nativeRenderableManager, int instances); @@ -893,12 +1000,15 @@ public long getNativeObject() { private static native int nSetBonesAsMatrices(long nativeObject, int i, Buffer matrices, int remaining, int boneCount, int offset); private static native int nSetBonesAsQuaternions(long nativeObject, int i, Buffer quaternions, int remaining, int boneCount, int offset); private static native void nSetMorphWeights(long nativeObject, int instance, float[] weights, int offset); - private static native void nSetMorphTargetBufferAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count); + private static native void nSetMorphTargetBufferOffsetAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset); private static native int nGetMorphTargetCount(long nativeObject, int i); private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez); private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value); private static native void nSetPriority(long nativeRenderableManager, int i, int priority); + private static native void nSetChannel(long nativeRenderableManager, int i, int channel); private static native void nSetCulling(long nativeRenderableManager, int i, boolean enabled); + private static native void nSetFogEnabled(long nativeRenderableManager, int i, boolean enabled); + private static native boolean nGetFogEnabled(long nativeRenderableManager, int i); private static native void nSetLightChannel(long nativeRenderableManager, int i, int channel, boolean enable); private static native boolean nGetLightChannel(long nativeRenderableManager, int i, int channel); private static native void nSetCastShadows(long nativeRenderableManager, int i, boolean enabled); @@ -909,6 +1019,7 @@ public long getNativeObject() { private static native void nGetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float[] center, float[] halfExtent); private static native int nGetPrimitiveCount(long nativeRenderableManager, int i); private static native void nSetMaterialInstanceAt(long nativeRenderableManager, int i, int primitiveIndex, long nativeMaterialInstance); + private static native void nClearMaterialInstanceAt(long nativeRenderableManager, int i, int primitiveIndex); private static native long nGetMaterialInstanceAt(long nativeRenderableManager, int i, int primitiveIndex); private static native void nSetGeometryAt(long nativeRenderableManager, int i, int primitiveIndex, int primitiveType, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int count); private static native void nSetBlendOrderAt(long nativeRenderableManager, int i, int primitiveIndex, int blendOrder); diff --git a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java index 3a9b6ab94db..509a3c080ce 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java @@ -101,7 +101,7 @@ public static class FrameRateOptions { /** * Desired frame interval in unit of 1 / DisplayInfo.refreshRate. */ - public float interval = 1.0f / 60.0f; + public float interval = 1.0f; /** * Additional headroom for the GPU as a ratio of the targetFrameTime. @@ -284,6 +284,33 @@ public void setPresentationTime(long monotonicClockNanos) { nSetPresentationTime(getNativeObject(), monotonicClockNanos); } + /** + * The use of this method is optional. It sets the VSYNC time expressed as the duration in + * nanosecond since epoch of std::chrono::steady_clock. + * If called, passing 0 to frameTimeNanos in Renderer.BeginFrame will use this + * time instead. + * @param steadyClockTimeNano duration in nanosecond since epoch of std::chrono::steady_clock + * @see Engine#getSteadyClockTimeNano + * @see Renderer#beginFrame + */ + public void setVsyncTime(long steadyClockTimeNano) { + nSetVsyncTime(getNativeObject(), steadyClockTimeNano); + } + + /** + * Call skipFrame when momentarily skipping frames, for instance if the content of the + * scene doesn't change. + * + * @param vsyncSteadyClockTimeNano The time in nanoseconds when the frame started being rendered, + * in the {@link System#nanoTime()} timebase. Divide this value by 1000000 to + * convert it to the {@link android.os.SystemClock#uptimeMillis()} + * time base. This typically comes from + * {@link android.view.Choreographer.FrameCallback}. + */ + public void skipFrame(long vsyncSteadyClockTimeNano) { + nSkipFrame(getNativeObject(), vsyncSteadyClockTimeNano); + } + /** * Sets up a frame for thisRenderer
.
* beginFrame
manages frame pacing, and returns whether or not a frame should be
@@ -702,6 +729,8 @@ void clearNativeObject() {
}
private static native void nSetPresentationTime(long nativeObject, long monotonicClockNanos);
+ private static native void nSetVsyncTime(long nativeObject, long steadyClockTimeNano);
+ private static native void nSkipFrame(long nativeObject, long vsyncSteadyClockTimeNano);
private static native boolean nBeginFrame(long nativeRenderer, long nativeSwapChain, long frameTimeNanos);
private static native void nEndFrame(long nativeRenderer);
private static native void nRender(long nativeRenderer, long nativeView);
diff --git a/android/filament-android/src/main/java/com/google/android/filament/Scene.java b/android/filament-android/src/main/java/com/google/android/filament/Scene.java
index 283b7024e73..9a41a4f64e5 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/Scene.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/Scene.java
@@ -16,6 +16,7 @@
package com.google.android.filament;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
@@ -146,18 +147,29 @@ public void removeEntities(@Entity int[] entities) {
}
/**
- * Returns the number of {@link RenderableManager} components in the Scene
.
+ * Returns the total number of Entities in the Scene
, whether alive or not.
*
- * @return number of {@link RenderableManager} components in the Scene
..
+ * @return the total number of Entities in the Scene
.
+ */
+ public int getEntityCount() {
+ return nGetEntityCount(getNativeObject());
+ }
+
+ /**
+ * Returns the number of active (alive) {@link RenderableManager} components in the
+ * Scene
.
+ *
+ * @return number of {@link RenderableManager} components in the Scene
.
*/
public int getRenderableCount() {
return nGetRenderableCount(getNativeObject());
}
/**
- * Returns the number of {@link LightManager} components in the Scene
.
+ * Returns the number of active (alive) {@link LightManager} components in the
+ * Scene
.
*
- * @return number of {@link LightManager} components in the Scene
..
+ * @return number of {@link LightManager} components in the Scene
.
*/
public int getLightCount() {
return nGetLightCount(getNativeObject());
@@ -179,6 +191,52 @@ public long getNativeObject() {
return mNativeObject;
}
+ /**
+ * Returns the list of all entities in the Scene. If outArray is provided and large enough,
+ * it is used to store the list and returned, otherwise a new array is allocated and returned.
+ * @param outArray an array to store the list of entities in the scene.
+ * @return outArray if it was used or a newly allocated array.
+ * @see #getEntityCount
+ */
+ public int[] getEntities(@Nullable int[] outArray) {
+ int c = getEntityCount();
+ if (outArray == null || outArray.length < c) {
+ outArray = new int[c];
+ }
+ boolean success = nGetEntities(getNativeObject(), outArray, outArray.length);
+ if (!success) {
+ throw new IllegalStateException("Error retriving Scene's entities");
+ }
+ return outArray;
+ }
+
+ /**
+ * Returns the list of all entities in the Scene in a newly allocated array.
+ * @return an array containing the list of all entities in the scene.
+ * @see #getEntityCount
+ */
+ public int[] getEntities() {
+ return getEntities(null);
+ }
+
+ public interface EntityProcessor {
+ void process(@Entity int entity);
+ }
+
+ /**
+ * Invokes user functor on each entity in the scene.
+ *
+ * It is not allowed to add or remove an entity from the scene within the functor.
+ *
+ * @param entityProcessor User provided functor called for each entity in the scene
+ */
+ public void forEach(@NonNull EntityProcessor entityProcessor) {
+ int[] entities = getEntities(null);
+ for (int entity : entities) {
+ entityProcessor.process(entity);
+ }
+ }
+
void clearNativeObject() {
mNativeObject = 0;
}
@@ -189,7 +247,9 @@ void clearNativeObject() {
private static native void nAddEntities(long nativeScene, int[] entities);
private static native void nRemove(long nativeScene, int entity);
private static native void nRemoveEntities(long nativeScene, int[] entities);
+ private static native int nGetEntityCount(long nativeScene);
private static native int nGetRenderableCount(long nativeScene);
private static native int nGetLightCount(long nativeScene);
private static native boolean nHasEntity(long nativeScene, int entity);
+ private static native boolean nGetEntities(long nativeScene, int[] outArray, int length);
}
diff --git a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java
index 391f1f6e1dc..db47d215635 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java
@@ -68,32 +68,33 @@ public class SwapChain {
private final Object mSurface;
private long mNativeObject;
- public static final long CONFIG_DEFAULT = 0x0;
-
- /**
- * This flag indicates that the SwapChain
must be allocated with an
- * alpha-channel.
- */
- public static final long CONFIG_TRANSPARENT = 0x1;
+ SwapChain(long nativeSwapChain, Object surface) {
+ mNativeObject = nativeSwapChain;
+ mSurface = surface;
+ }
/**
- * This flag indicates that the SwapChain
may be used as a source surface
- * for reading back render results. This config must be set when creating
- * any SwapChain
that will be used as the source for a blit operation.
+ * Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag.
+ * The default implementation returns false.
*
- * @see Renderer#copyFrame
+ * @param engine A reference to the filament Engine
+ * @return true if CONFIG_PROTECTED_CONTENT is supported, false otherwise.
+ * @see SwapChainFlags#CONFIG_PROTECTED_CONTENT
*/
- public static final long CONFIG_READABLE = 0x2;
+ public static boolean isProtectedContentSupported(@NonNull Engine engine) {
+ return nIsProtectedContentSupported(engine.getNativeObject());
+ }
/**
- * Indicates that the native X11 window is an XCB window rather than an XLIB window.
- * This is ignored on non-Linux platforms and in builds that support only one X11 API.
+ * Return whether createSwapChain supports the CONFIG_SRGB_COLORSPACE flag.
+ * The default implementation returns false.
+ *
+ * @param engine A reference to the filament Engine
+ * @return true if CONFIG_SRGB_COLORSPACE is supported, false otherwise.
+ * @see SwapChainFlags#CONFIG_SRGB_COLORSPACE
*/
- public static final long CONFIG_ENABLE_XCB = 0x4;
-
- SwapChain(long nativeSwapChain, Object surface) {
- mNativeObject = nativeSwapChain;
- mSurface = surface;
+ public static boolean isSRGBSwapChainSupported(@NonNull Engine engine) {
+ return nIsSRGBSwapChainSupported(engine.getNativeObject());
}
/**
@@ -114,10 +115,6 @@ public Object getNativeWindow() {
*
- * The FrameCompletedCallback is guaranteed to be called on the main Filament thread. - *
- * - ** Warning: Only Filament's Metal backend supports frame callbacks. Other backends ignore the * callback (which will never be called) and proceed normally. *
@@ -141,4 +138,6 @@ void clearNativeObject() { } private static native void nSetFrameCompletedCallback(long nativeSwapChain, Object handler, Runnable callback); + private static native boolean nIsSRGBSwapChainSupported(long nativeEngine); + private static native boolean nIsProtectedContentSupported(long nativeEngine); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java b/android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java new file mode 100644 index 00000000000..daef2dd15f2 --- /dev/null +++ b/android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.filament; + +// Note: SwapChainFlags is kept separate from SwapChain so that UiHelper does not need to depend +// on SwapChain. This allows clients to use UiHelper without requiring all of Filament's Java +// classes. + +/** + * Flags that aSwapChain
can be created with to control behavior.
+ *
+ * @see Engine#createSwapChain
+ * @see Engine#createSwapChainFromNativeSurface
+ */
+public final class SwapChainFlags {
+
+ public static final long CONFIG_DEFAULT = 0x0;
+
+ /**
+ * This flag indicates that the SwapChain
must be allocated with an
+ * alpha-channel.
+ */
+ public static final long CONFIG_TRANSPARENT = 0x1;
+
+ /**
+ * This flag indicates that the SwapChain
may be used as a source surface
+ * for reading back render results. This config must be set when creating
+ * any SwapChain
that will be used as the source for a blit operation.
+ *
+ * @see Renderer#copyFrame
+ */
+ public static final long CONFIG_READABLE = 0x2;
+
+ /**
+ * Indicates that the native X11 window is an XCB window rather than an XLIB window.
+ * This is ignored on non-Linux platforms and in builds that support only one X11 API.
+ */
+ public static final long CONFIG_ENABLE_XCB = 0x4;
+
+ /**
+ * Indicates that the SwapChain must automatically perform linear to sRGB encoding.
+ *
+ * This flag is ignored if isSRGBSwapChainSupported() is false.
+ *
+ * When using this flag, post-processing should be disabled.
+ *
+ * @see SwapChain#isSRGBSwapChainSupported
+ * @see View#setPostProcessingEnabled
+ */
+ public static final long CONFIG_SRGB_COLORSPACE = 0x10;
+
+ /**
+ * Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer.
+ *
+ * This flag is necessary when using View::setStencilBufferEnabled and rendering directly into
+ * the SwapChain (when post-processing is disabled).
+ *
+ * The specific format of the stencil buffer depends on platform support. The following pixel
+ * formats are tried, in order of preference:
+ *
+ * Depth only (without CONFIG_HAS_STENCIL_BUFFER):
+ * - DEPTH32F
+ * - DEPTH24
+ *
+ * Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER):
+ * - DEPTH32F_STENCIL8
+ * - DEPTH24F_STENCIL8
+ *
+ * Note that enabling the stencil buffer may hinder depth precision and should only be used if
+ * necessary.
+ *
+ * @see View#setStencilBufferEnabled
+ * @see View#setPostProcessingEnabled
+ */
+ public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20;
+
+ /**
+ * The SwapChain contains protected content. Only supported when isProtectedContentSupported()
+ * is true.
+ */
+ public static final long CONFIG_PROTECTED_CONTENT = 0x40;
+}
+
diff --git a/android/filament-android/src/main/java/com/google/android/filament/Texture.java b/android/filament-android/src/main/java/com/google/android/filament/Texture.java
index db312a93442..eaf81adc3cf 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/Texture.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/Texture.java
@@ -800,6 +800,19 @@ public Builder importTexture(long id) {
return this;
}
+ /**
+ * Creates an external texture. The content must be set using setExternalImage().
+ * The sampler can be SAMPLER_EXTERNAL or SAMPLER_2D depending on the format. Generally
+ * YUV formats must use SAMPLER_EXTERNAL. This depends on the backend features and is not
+ * validated.
+ * @return This Builder, for chaining calls.
+ */
+ @NonNull
+ public Builder external() {
+ nBuilderExternal(mNativeBuilder);
+ return this;
+ }
+
/**
* Creates a new Texture
instance.
* @param engine The {@link Engine} to associate this Texture
with.
@@ -849,6 +862,10 @@ public static class Usage {
public static final int SAMPLEABLE = 0x10;
/** Texture can be used as a subpass input */
public static final int SUBPASS_INPUT = 0x20;
+ /** Texture can be used the source of a blit() */
+ public static final int BLIT_SRC = 0x40;
+ /** Texture can be used the destination of a blit() */
+ public static final int BLIT_DST = 0x80;
/** by default textures are UPLOADABLE
and SAMPLEABLE
*/
public static final int DEFAULT = UPLOADABLE | SAMPLEABLE;
}
@@ -1257,6 +1274,7 @@ void clearNativeObject() {
private static native void nBuilderUsage(long nativeBuilder, int flags);
private static native void nBuilderSwizzle(long nativeBuilder, int r, int g, int b, int a);
private static native void nBuilderImportTexture(long nativeBuilder, long id);
+ private static native void nBuilderExternal(long nativeBuilder);
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);
private static native int nGetWidth(long nativeTexture, int level);
diff --git a/android/filament-android/src/main/java/com/google/android/filament/TextureSampler.java b/android/filament-android/src/main/java/com/google/android/filament/TextureSampler.java
index 3dbdfb58cff..1c42f1086c7 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/TextureSampler.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/TextureSampler.java
@@ -126,7 +126,7 @@ public enum CompareFunction {
NEVER
}
- int mSampler = 0; // bit field used by native
+ long mSampler = 0; // bit field used by native
/**
* Initializes the TextureSampler
with default values.
@@ -276,7 +276,7 @@ public WrapMode getWrapModeR() {
}
/**
- * Sets the wrapping mode in the t (depth) direction.
+ * Sets the wrapping mode in the r (depth) direction.
* @param mode wrapping mode
*/
public void setWrapModeR(WrapMode mode) {
@@ -342,26 +342,26 @@ private static MinFilter minFilterFromMagFilter(@NonNull MagFilter minMag) {
}
}
- private static native int nCreateSampler(int min, int max, int s, int t, int r);
- private static native int nCreateCompareSampler(int mode, int function);
+ private static native long nCreateSampler(int min, int max, int s, int t, int r);
+ private static native long nCreateCompareSampler(int mode, int function);
- private static native int nGetMinFilter(int sampler);
- private static native int nSetMinFilter(int sampler, int filter);
- private static native int nGetMagFilter(int sampler);
- private static native int nSetMagFilter(int sampler, int filter);
+ private static native int nGetMinFilter(long sampler);
+ private static native long nSetMinFilter(long sampler, int filter);
+ private static native int nGetMagFilter(long sampler);
+ private static native long nSetMagFilter(long sampler, int filter);
- private static native int nGetWrapModeS(int sampler);
- private static native int nSetWrapModeS(int sampler, int mode);
- private static native int nGetWrapModeT(int sampler);
- private static native int nSetWrapModeT(int sampler, int mode);
- private static native int nGetWrapModeR(int sampler);
- private static native int nSetWrapModeR(int sampler, int mode);
+ private static native int nGetWrapModeS(long sampler);
+ private static native long nSetWrapModeS(long sampler, int mode);
+ private static native int nGetWrapModeT(long sampler);
+ private static native long nSetWrapModeT(long sampler, int mode);
+ private static native int nGetWrapModeR(long sampler);
+ private static native long nSetWrapModeR(long sampler, int mode);
- private static native int nGetCompareMode(int sampler);
- private static native int nSetCompareMode(int sampler, int mode);
- private static native int nGetCompareFunction(int sampler);
- private static native int nSetCompareFunction(int sampler, int function);
+ private static native int nGetCompareMode(long sampler);
+ private static native long nSetCompareMode(long sampler, int mode);
+ private static native int nGetCompareFunction(long sampler);
+ private static native long nSetCompareFunction(long sampler, int function);
- private static native float nGetAnisotropy(int sampler);
- private static native int nSetAnisotropy(int sampler, float anisotropy);
+ private static native float nGetAnisotropy(long sampler);
+ private static native long nSetAnisotropy(long sampler, float anisotropy);
}
diff --git a/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java b/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java
index 15800562e3e..c3d3490441b 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java
@@ -16,12 +16,14 @@
* + * Transparent picking will create an extra pass for rendering depth + * from both transparent and opaque renderables. + *
+ * + * @param enabled true enables transparent picking, false disables it. + */ + public void setTransparentPickingEnabled(boolean enabled) { + nSetTransparentPickingEnabled(getNativeObject(), enabled); + } + /** * Sets options relative to dynamic lighting for this view. * @@ -902,7 +941,7 @@ public void setBloomOptions(@NonNull BloomOptions options) { mBloomOptions = options; nSetBloomOptions(getNativeObject(), options.dirt != null ? options.dirt.getNativeObject() : 0, options.dirtStrength, options.strength, options.resolution, - options.anamorphism, options.levels, options.blendMode.ordinal(), + options.levels, options.blendMode.ordinal(), options.threshold, options.enabled, options.highlight, options.lensFlare, options.starburst, options.chromaticAberration, options.ghostCount, options.ghostSpacing, options.ghostThreshold, @@ -962,9 +1001,11 @@ public void setFogOptions(@NonNull FogOptions options) { assertFloat3In(options.color); mFogOptions = options; nSetFogOptions(getNativeObject(), options.distance, options.maximumOpacity, options.height, - options.heightFalloff, options.color[0], options.color[1], options.color[2], + options.heightFalloff, options.cutOffDistance, + options.color[0], options.color[1], options.color[2], options.density, options.inScatteringStart, options.inScatteringSize, options.fogColorFromIbl, + options.skyColor == null ? 0 : options.skyColor.getNativeObject(), options.enabled); } @@ -1028,7 +1069,8 @@ public DepthOfFieldOptions getDepthOfFieldOptions() { * * *- * Post-processing must be enabled in order to use the stencil buffer. + * If post-processing is disabled, then the SwapChain must have the CONFIG_HAS_STENCIL_BUFFER + * flag set in order to use the stencil buffer. *
* *@@ -1050,6 +1092,51 @@ public boolean isStencilBufferEnabled() { return nIsStencilBufferEnabled(getNativeObject()); } + /** + * Sets the stereoscopic rendering options for this view. + * + *
+ * Currently, only one type of stereoscopic rendering is supported: side-by-side. + * Side-by-side stereo rendering splits the viewport into two halves: a left and right half. + * Eye 0 will render to the left half, while Eye 1 will render into the right half. + *
+ * + *+ * Currently, the following features are not supported with stereoscopic rendering: + * - post-processing + * - shadowing + * - punctual lights + *
+ * + *+ * Stereo rendering depends on device and platform support. To check if stereo rendering is + * supported, use {@link Engine#isStereoSupported()}. If stereo rendering is not supported, then + * the stereoscopic options have no effect. + *
+ * + * @param options The stereoscopic options to use on this view + * @see #getStereoscopicOptions + */ + public void setStereoscopicOptions(@NonNull StereoscopicOptions options) { + mStereoscopicOptions = options; + nSetStereoscopicOptions(getNativeObject(), options.enabled); + } + + /** + * Gets the stereoscopic options. + * + * @return options Stereoscopic options currently set. + * @see #setStereoscopicOptions + */ + @NonNull + public StereoscopicOptions getStereoscopicOptions() { + if (mStereoscopicOptions == null) { + mStereoscopicOptions = new StereoscopicOptions(); + } + return mStereoscopicOptions; + } + + /** * A class containing the result of a picking query */ @@ -1094,10 +1181,29 @@ public void pick(int x, int y, nPick(getNativeObject(), x, y, handler, internalCallback); } + @UsedByNative("View.cpp") private static class InternalOnPickCallback implements Runnable { + private final OnPickCallback mUserCallback; + private final PickingQueryResult mPickingQueryResult = new PickingQueryResult(); + + @UsedByNative("View.cpp") + @Entity + int mRenderable; + + @UsedByNative("View.cpp") + float mDepth; + + @UsedByNative("View.cpp") + float mFragCoordsX; + @UsedByNative("View.cpp") + float mFragCoordsY; + @UsedByNative("View.cpp") + float mFragCoordsZ; + public InternalOnPickCallback(OnPickCallback mUserCallback) { this.mUserCallback = mUserCallback; } + @Override public void run() { mPickingQueryResult.renderable = mRenderable; @@ -1107,13 +1213,63 @@ public void run() { mPickingQueryResult.fragCoords[2] = mFragCoordsZ; mUserCallback.onPick(mPickingQueryResult); } - private final OnPickCallback mUserCallback; - private final PickingQueryResult mPickingQueryResult = new PickingQueryResult(); - @Entity int mRenderable; - float mDepth; - float mFragCoordsX; - float mFragCoordsY; - float mFragCoordsZ; + } + + /** + * Set the value of material global variables. There are up-to four such variable each of + * type float4. These variables can be read in a user Material with + * `getMaterialGlobal{0|1|2|3}()`. All variable start with a default value of { 0, 0, 0, 1 } + * + * @param index index of the variable to set between 0 and 3. + * @param value new value for the variable. + * @see #getMaterialGlobal + */ + public void setMaterialGlobal(int index, @NonNull @Size(min = 4) float[] value) { + Asserts.assertFloat4In(value); + nSetMaterialGlobal(getNativeObject(), index, value[0], value[1], value[2], value[3]); + } + + /** + * Get the value of the material global variables. + * All variable start with a default value of { 0, 0, 0, 1 } + * + * @param index index of the variable to set between 0 and 3. + * @param out A 4-float array where the value will be stored, or null in which case the array is + * allocated. + * @return A 4-float array containing the current value of the variable. + * @see #setMaterialGlobal + */ + @NonNull @Size(min = 4) + public float[] getMaterialGlobal(int index, @Nullable @Size(min = 4) float[] out) { + out = Asserts.assertFloat4(out); + nGetMaterialGlobal(getNativeObject(), index, out); + return out; + } + + /** + * Get an Entity representing the large scale fog object. + * This entity is always inherited by the View's Scene. + * + * It is for example possible to create a TransformManager component with this + * Entity and apply a transformation globally on the fog. + * + * @return an Entity representing the large scale fog object. + */ + @Entity + public int getFogEntity() { + return nGetFogEntity(getNativeObject()); + } + + /** + * When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view + * keeps a history of previous frame renders associated with the Renderer the view was last + * used with. When switching Renderer, it may be necessary to clear that history by calling + * this method. Similarly, if the whole content of the screen change, like when a cut-scene + * starts, clearing the history might be needed to avoid artifacts due to the previous frame + * being very different. + */ + public void clearFrameHistory(Engine engine) { + nClearFrameHistory(getNativeObject(), engine.getNativeObject()); } public long getNativeObject() { @@ -1130,6 +1286,7 @@ void clearNativeObject() { private static native void nSetName(long nativeView, String name); private static native void nSetScene(long nativeView, long nativeScene); private static native void nSetCamera(long nativeView, long nativeCamera); + private static native boolean nHasCamera(long nativeView); private static native void nSetViewport(long nativeView, int left, int bottom, int width, int height); private static native void nSetVisibleLayers(long nativeView, int select, int value); private static native void nSetShadowingEnabled(long nativeView, boolean enabled); @@ -1151,13 +1308,16 @@ void clearNativeObject() { private static native boolean nIsPostProcessingEnabled(long nativeView); private static native void nSetFrontFaceWindingInverted(long nativeView, boolean inverted); private static native boolean nIsFrontFaceWindingInverted(long nativeView); + private static native void nSetTransparentPickingEnabled(long nativeView, boolean enabled); + private static native boolean nIsTransparentPickingEnabled(long nativeView); private static native void nSetAmbientOcclusion(long nativeView, int ordinal); private static native int nGetAmbientOcclusion(long nativeView); private static native void nSetAmbientOcclusionOptions(long nativeView, float radius, float bias, float power, float resolution, float intensity, float bilateralThreshold, int quality, int lowPassFilter, int upsampling, boolean enabled, boolean bentNormals, float minHorizonAngleRad); private static native void nSetSSCTOptions(long nativeView, float ssctLightConeRad, float ssctStartTraceDistance, float ssctContactDistanceMax, float ssctIntensity, float v, float v1, float v2, float ssctDepthBias, float ssctDepthSlopeBias, int ssctSampleCount, int ssctRayCount, boolean ssctEnabled); - private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, float anamorphism, int levels, int blendMode, boolean threshold, boolean enabled, float highlight, + private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, int levels, int blendMode, boolean threshold, boolean enabled, float highlight, boolean lensFlare, boolean starburst, float chromaticAberration, int ghostCount, float ghostSpacing, float ghostThreshold, float haloThickness, float haloRadius, float haloThreshold); - private static native void nSetFogOptions(long nativeView, float distance, float maximumOpacity, float height, float heightFalloff, float v, float v1, float v2, float density, float inScatteringStart, float inScatteringSize, boolean fogColorFromIbl, boolean enabled); + private static native void nSetFogOptions(long nativeView, float distance, float maximumOpacity, float height, float heightFalloff, float cutOffDistance, float v, float v1, float v2, float density, float inScatteringStart, float inScatteringSize, boolean fogColorFromIbl, long skyColorNativeObject, boolean enabled); + private static native void nSetStereoscopicOptions(long nativeView, boolean enabled); private static native void nSetBlendMode(long nativeView, int blendMode); private static native void nSetDepthOfFieldOptions(long nativeView, float cocScale, float maxApertureDiameter, boolean enabled, int filter, boolean nativeResolution, int foregroundRingCount, int backgroundRingCount, int fastGatherRingCount, int maxForegroundCOC, int maxBackgroundCOC); @@ -1172,6 +1332,10 @@ private static native void nSetDepthOfFieldOptions(long nativeView, float cocSca private static native void nPick(long nativeView, int x, int y, Object handler, InternalOnPickCallback internalCallback); private static native void nSetStencilBufferEnabled(long nativeView, boolean enabled); private static native boolean nIsStencilBufferEnabled(long nativeView); + private static native void nSetMaterialGlobal(long nativeView, int index, float x, float y, float z, float w); + private static native void nGetMaterialGlobal(long nativeView, int index, float[] out); + private static native int nGetFogEntity(long nativeView); + private static native void nClearFrameHistory(long nativeView, long nativeEngine); /** * List of available ambient occlusion techniques. @@ -1288,8 +1452,6 @@ public static class DynamicResolutionOptions { * blendMode: Whether the bloom effect is purely additive (false) or mixed with the original * image (true). * - * anamorphism: Bloom's aspect ratio (x/y), for artistic purposes. - * * threshold: When enabled, a threshold at 1.0 is applied on the source image, this is * useful for artistic reasons and is usually needed when a dirt texture is used. * @@ -1327,13 +1489,9 @@ public enum BlendMode { /** * resolution of vertical axis (2^levels to 2048) */ - public int resolution = 360; + public int resolution = 384; /** - * bloom x/y aspect-ratio (1/32 to 32) - */ - public float anamorphism = 1.0f; - /** - * number of blur levels (3 to 11) + * number of blur levels (1 to 11) */ public int levels = 6; /** @@ -1353,6 +1511,17 @@ public enum BlendMode { * limit highlights to this value before bloom [10, +inf] */ public float highlight = 1000.0f; + /** + * Bloom quality level. + * LOW (default): use a more optimized down-sampling filter, however there can be artifacts + * with dynamic resolution, this can be alleviated by using the homogenous mode. + * MEDIUM: Good balance between quality and performance. + * HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts. + * This mode can be significantly slower on mobile, especially at high resolution. + * This mode greatly improves the anamorphic bloom. + */ + @NonNull + public QualityLevel quality = QualityLevel.LOW; /** * enable screen-space lens flare */ @@ -1392,48 +1561,109 @@ public enum BlendMode { } /** - * Options to control fog in the scene + * Options to control large-scale fog in the scene */ public static class FogOptions { /** - * distance in world units from the camera where the fog starts ( >= 0.0 ) + * Distance in world units [m] from the camera to where the fog starts ( >= 0.0 ) */ public float distance = 0.0f; + /** + * Distance in world units [m] after which the fog calculation is disabled. + * This can be used to exclude the skybox, which is desirable if it already contains clouds or + * fog. The default value is +infinity which applies the fog to everything. + * + * Note: The SkyBox is typically at a distance of 1e19 in world space (depending on the near + * plane distance and projection used though). + */ + public float cutOffDistance = Float.POSITIVE_INFINITY; /** * fog's maximum opacity between 0 and 1 */ public float maximumOpacity = 1.0f; /** - * fog's floor in world units + * Fog's floor in world units [m]. This sets the "sea level". */ public float height = 0.0f; /** - * how fast fog dissipates with altitude + * How fast the fog dissipates with altitude. heightFalloff has a unit of [1/m]. + * It can be expressed as 1/H, where H is the altitude change in world units [m] that causes a + * factor 2.78 (e) change in fog density. + * + * A falloff of 0 means the fog density is constant everywhere and may result is slightly + * faster computations. */ public float heightFalloff = 1.0f; /** - * fog's color (linear), see fogColorFromIbl + * Fog's color is used for ambient light in-scattering, a good value is + * to use the average of the ambient light, possibly tinted towards blue + * for outdoors environments. Color component's values should be between 0 and 1, values + * above one are allowed but could create a non energy-conservative fog (this is dependant + * on the IBL's intensity as well). + * + * We assume that our fog has no absorption and therefore all the light it scatters out + * becomes ambient light in-scattering and has lost all directionality, i.e.: scattering is + * isotropic. This somewhat simulates Rayleigh scattering. + * + * This value is used as a tint instead, when fogColorFromIbl is enabled. + * + * @see fogColorFromIbl */ @NonNull @Size(min = 3) - public float[] color = {0.5f, 0.5f, 0.5f}; + public float[] color = {1.0f, 1.0f, 1.0f}; /** - * fog's density at altitude given by 'height' + * Extinction factor in [1/m] at altitude 'height'. The extinction factor controls how much + * light is absorbed and out-scattered per unit of distance. Each unit of extinction reduces + * the incoming light to 37% of its original value. + * + * Note: The extinction factor is related to the fog density, it's usually some constant K times + * the density at sea level (more specifically at fog height). The constant K depends on + * the composition of the fog/atmosphere. + * + * For historical reason this parameter is called `density`. */ public float density = 0.1f; /** - * distance in world units from the camera where in-scattering starts + * Distance in world units [m] from the camera where the Sun in-scattering starts. */ public float inScatteringStart = 0.0f; /** - * size of in-scattering (>0 to activate). Good values are >> 1 (e.g. ~10 - 100). + * Very inaccurately simulates the Sun's in-scattering. That is, the light from the sun that + * is scattered (by the fog) towards the camera. + * Size of the Sun in-scattering (>0 to activate). Good values are >> 1 (e.g. ~10 - 100). + * Smaller values result is a larger scattering size. */ public float inScatteringSize = -1.0f; /** - * Fog color will be modulated by the IBL color in the view direction. + * The fog color will be sampled from the IBL in the view direction and tinted by `color`. + * Depending on the scene this can produce very convincing results. + * + * This simulates a more anisotropic phase-function. + * + * `fogColorFromIbl` is ignored when skyTexture is specified. + * + * @see skyColor */ public boolean fogColorFromIbl = false; /** - * enable or disable fog + * skyTexture must be a mipmapped cubemap. When provided, the fog color will be sampled from + * this texture, higher resolution mip levels will be used for objects at the far clip plane, + * and lower resolution mip levels for objects closer to the camera. The skyTexture should + * typically be heavily blurred; a typical way to produce this texture is to blur the base + * level with a strong gaussian filter or even an irradiance filter and then generate mip + * levels as usual. How blurred the base level is somewhat of an artistic decision. + * + * This simulates a more anisotropic phase-function. + * + * `fogColorFromIbl` is ignored when skyTexture is specified. + * + * @see Texture + * @see fogColorFromIbl + */ + @Nullable + public Texture skyColor = null; + /** + * Enable or disable large-scale fog */ public boolean enabled = false; } @@ -1458,6 +1688,10 @@ public enum Filter { * circle of confusion scale factor (amount of blur) */ public float cocScale = 1.0f; + /** + * width/height aspect ratio of the circle of confusion (simulate anamorphic lenses) + */ + public float cocAspectRatio = 1.0f; /** * maximum aperture diameter in meters (zero to disable rotation) */ @@ -1673,7 +1907,7 @@ public static class AmbientOcclusionOptions { } /** - * Options for Temporal Multi-Sample Anti-aliasing (MSAA) + * Options for Multi-Sample Anti-aliasing (MSAA) * @see setMultiSampleAntiAliasingOptions() */ public static class MultiSampleAntiAliasingOptions { @@ -1697,21 +1931,111 @@ public static class MultiSampleAntiAliasingOptions { /** * Options for Temporal Anti-aliasing (TAA) + * Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process + * shaders to be recompiled. These options should be changed or set during initialization. + * `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time. + * + * `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state. + * see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information. + * * @see setTemporalAntiAliasingOptions() */ public static class TemporalAntiAliasingOptions { + public enum BoxType { + /** + * use an AABB neighborhood + */ + AABB, + /** + * use the variance of the neighborhood (not recommended) + */ + VARIANCE, + /** + * use both AABB and variance + */ + AABB_VARIANCE, + } + + public enum BoxClipping { + /** + * Accurate box clipping + */ + ACCURATE, + /** + * clamping + */ + CLAMP, + /** + * no rejections (use for debugging) + */ + NONE, + } + + public enum JitterPattern { + RGSS_X4, + UNIFORM_HELIX_X4, + HALTON_23_X8, + HALTON_23_X16, + HALTON_23_X32, + } + /** - * reconstruction filter width typically between 0 (sharper, aliased) and 1 (smoother) + * reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother) */ public float filterWidth = 1.0f; /** * history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA). */ - public float feedback = 0.04f; + public float feedback = 0.12f; + /** + * texturing lod bias (typically -1 or -2) + */ + public float lodBias = -1.0f; + /** + * post-TAA sharpen, especially useful when upscaling is true. + */ + public float sharpness = 0.0f; /** * enables or disables temporal anti-aliasing */ public boolean enabled = false; + /** + * 4x TAA upscaling. Disables Dynamic Resolution. [BETA] + */ + public boolean upscaling = false; + /** + * whether to filter the history buffer + */ + public boolean filterHistory = true; + /** + * whether to apply the reconstruction filter to the input + */ + public boolean filterInput = true; + /** + * whether to use the YcoCg color-space for history rejection + */ + public boolean useYCoCg = false; + /** + * type of color gamut box + */ + @NonNull + public TemporalAntiAliasingOptions.BoxType boxType = TemporalAntiAliasingOptions.BoxType.AABB; + /** + * clipping algorithm + */ + @NonNull + public TemporalAntiAliasingOptions.BoxClipping boxClipping = TemporalAntiAliasingOptions.BoxClipping.ACCURATE; + @NonNull + public TemporalAntiAliasingOptions.JitterPattern jitterPattern = TemporalAntiAliasingOptions.JitterPattern.HALTON_23_X16; + public float varianceGamma = 1.0f; + /** + * adjust the feedback dynamically to reduce flickering + */ + public boolean preventFlickering = false; + /** + * whether to apply history reprojection (debug option) + */ + public boolean historyReprojection = true; } /** @@ -1798,6 +2122,7 @@ public enum ShadowType { * PCF with soft shadows and contact hardening */ PCSS, + PCFd, } /** @@ -1860,4 +2185,11 @@ public static class SoftShadowOptions { */ public float penumbraRatioScale = 1.0f; } + + /** + * Options for stereoscopic (multi-eye) rendering. + */ + public static class StereoscopicOptions { + public boolean enabled = false; + } } diff --git a/android/filament-android/src/main/java/com/google/android/filament/android/FilamentHelper.java b/android/filament-android/src/main/java/com/google/android/filament/android/FilamentHelper.java new file mode 100644 index 00000000000..6cb738e7dff --- /dev/null +++ b/android/filament-android/src/main/java/com/google/android/filament/android/FilamentHelper.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.filament.android; + +import com.google.android.filament.Engine; +import com.google.android.filament.Fence; + +public class FilamentHelper { + + /** + * Wait for all pending frames to be processed before returning. This is to avoid a race + * between the surface being resized before pending frames are rendered into it. + *
+ * For {@link android.view.TextureView} this must be called before the texture's size is
+ * reconfigured, which unfortunately is done by the Android framework before
+ * {@link UiHelper} listeners are invoked. Therefore synchronizePendingFrames
+ * cannot be called from
+ * {@link android.view.TextureView.SurfaceTextureListener#onSurfaceTextureSizeChanged}; instead
+ * a subclass of {@link android.view.TextureView} must be used in order to call it from
+ * {@link android.view.TextureView#onSizeChanged}:
+ *
+ * public class MyTextureView extends TextureView { + * private Engine engine; + * protected void onSizeChanged(int w, int h, int oldw, int oldh) { + * FilamentHelper.synchronizePendingFrames(engine); + * super.onSizeChanged(w, h, oldw, oldh); + * } + * } + *+ * + * Otherwise, this is typically called from {@link UiHelper.RendererCallback#onResized}, + * {@link android.view.SurfaceHolder.Callback#surfaceChanged}. + * + * @param engine Filament engine to synchronize + * + * @see UiHelper.RendererCallback#onResized + * @see android.view.SurfaceHolder.Callback#surfaceChanged + * @see android.view.TextureView#onSizeChanged + */ + static public void synchronizePendingFrames(Engine engine) { + Fence fence = engine.createFence(); + fence.wait(Fence.Mode.FLUSH, Fence.WAIT_FOR_EVER); + engine.destroyFence(fence); + } +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java b/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java index ae1bdcd7c0b..4d6f3a140f1 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java +++ b/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java @@ -27,7 +27,7 @@ import android.view.SurfaceView; import android.view.TextureView; -import com.google.android.filament.SwapChain; +import com.google.android.filament.SwapChainFlags; /** * UiHelper is a simple class that can manage either a SurfaceView, TextureView, or a SurfaceHolder @@ -84,6 +84,14 @@ * // The native surface has changed size. This is always called at least once * // after the surface is created (after onNativeWindowChanged() is invoked). * public void onResized(int width, int height) { + * + * // Wait for all pending frames to be processed before returning. This is to + * // avoid a race between the surface being resized before pending frames are + * // rendered into it. + * Fence fence = mEngine.createFence(); + * fence.wait(Fence.Mode.FLUSH, Fence.WAIT_FOR_EVER); + * mEngine.destroyFence(fence); + * * // Compute camera projection and set the viewport on the view * } * }); @@ -174,28 +182,85 @@ private interface RenderSurface { void detach(); } - private static class SurfaceViewHandler implements RenderSurface { - private SurfaceView mSurfaceView; + private class SurfaceViewHandler implements RenderSurface, SurfaceHolder.Callback { + @NonNull private final SurfaceView mSurfaceView; + + SurfaceViewHandler(@NonNull SurfaceView surfaceView) { + mSurfaceView = surfaceView; + + @NonNull SurfaceHolder holder = surfaceView.getHolder(); + holder.addCallback(this); + + if (mDesiredWidth > 0 && mDesiredHeight > 0) { + holder.setFixedSize(mDesiredWidth, mDesiredHeight); + } - SurfaceViewHandler(SurfaceView surface) { - mSurfaceView = surface; + // in case the SurfaceView's surface already existed + final Surface surface = holder.getSurface(); + if (surface != null && surface.isValid()) { + surfaceCreated(holder); + // there is no way to retrieve the actual PixelFormat, since it is not used + // in the callback, we can use whatever we want. + surfaceChanged(holder, PixelFormat.RGBA_8888, + holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); + } } @Override public void resize(int width, int height) { - mSurfaceView.getHolder().setFixedSize(width, height); + @NonNull SurfaceHolder holder = mSurfaceView.getHolder(); + holder.setFixedSize(width, height); } @Override public void detach() { + @NonNull SurfaceHolder holder = mSurfaceView.getHolder(); + holder.removeCallback(this); + } + + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); + createSwapChain(holder.getSurface()); + } + + @Override + public void surfaceChanged( + @NonNull SurfaceHolder holder, int format, int width, int height) { + // Note: this is always called at least once after surfaceCreated() + if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); + if (mRenderCallback != null) { + mRenderCallback.onResized(width, height); + } + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { + if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); + destroySwapChain(); } } - private static class SurfaceHolderHandler implements RenderSurface { - private SurfaceHolder mSurfaceHolder; + private class SurfaceHolderHandler implements RenderSurface, SurfaceHolder.Callback { + private final SurfaceHolder mSurfaceHolder; + + SurfaceHolderHandler(@NonNull SurfaceHolder holder) { + mSurfaceHolder = holder; + holder.addCallback(this); + + if (mDesiredWidth > 0 && mDesiredHeight > 0) { + holder.setFixedSize(mDesiredWidth, mDesiredHeight); + } - SurfaceHolderHandler(SurfaceHolder surface) { - mSurfaceHolder = surface; + // in case the SurfaceHolder's surface already existed + final Surface surface = holder.getSurface(); + if (surface != null && surface.isValid()) { + surfaceCreated(holder); + // there is no way to retrieve the actual PixelFormat, since it is not used + // in the callback, we can use whatever we want. + surfaceChanged(holder, PixelFormat.RGBA_8888, + holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); + } } @Override @@ -205,30 +270,127 @@ public void resize(int width, int height) { @Override public void detach() { + mSurfaceHolder.removeCallback(this); + } + + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); + createSwapChain(holder.getSurface()); + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { + // Note: this is always called at least once after surfaceCreated() + if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); + if (mRenderCallback != null) { + mRenderCallback.onResized(width, height); + } + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) { + if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); + destroySwapChain(); } } - private class TextureViewHandler implements RenderSurface { - private TextureView mTextureView; + private class TextureViewHandler implements RenderSurface, TextureView.SurfaceTextureListener { + private final TextureView mTextureView; private Surface mSurface; - TextureViewHandler(TextureView surface) { mTextureView = surface; } + TextureViewHandler(@NonNull TextureView view) { + mTextureView = view; + mTextureView.setSurfaceTextureListener(this); + // in case the View's SurfaceTexture already existed + if (view.isAvailable()) { + SurfaceTexture surfaceTexture = view.getSurfaceTexture(); + if (surfaceTexture != null) { + this.onSurfaceTextureAvailable(surfaceTexture, + mDesiredWidth, mDesiredHeight); + } + } + } @Override public void resize(int width, int height) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height); + final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); + if (surfaceTexture != null) { + surfaceTexture.setDefaultBufferSize(width, height); + } + } + if (mRenderCallback != null) { + // the call above won't cause TextureView.onSurfaceTextureSizeChanged() + mRenderCallback.onResized(width, height); } - // the call above won't cause TextureView.onSurfaceTextureSizeChanged() - mRenderCallback.onResized(width, height); } @Override public void detach() { + mTextureView.setSurfaceTextureListener(null); + } + + + @Override + public void onSurfaceTextureAvailable( + @NonNull SurfaceTexture surfaceTexture, int width, int height) { + if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + if (mDesiredWidth > 0 && mDesiredHeight > 0) { + surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); + } + } + + final Surface surface = new Surface(surfaceTexture); + setSurface(surface); + createSwapChain(surface); + + if (mRenderCallback != null) { + // Call this the first time because onSurfaceTextureSizeChanged() + // isn't called at initialization time + mRenderCallback.onResized(width, height); + } + } + + @Override + public void onSurfaceTextureSizeChanged( + @NonNull SurfaceTexture surfaceTexture, int width, int height) { + if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()"); + if (mRenderCallback != null) { + if (mDesiredWidth > 0 && mDesiredHeight > 0) { + surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); + mRenderCallback.onResized(mDesiredWidth, mDesiredHeight); + } else { + mRenderCallback.onResized(width, height); + } + // We must recreate the SwapChain to guarantee that it sees the new size. + // More precisely, for an EGL client, the EGLSurface must be recreated. For + // a Vulkan client, the SwapChain must be recreated. Calling + // onNativeWindowChanged() will accomplish that. + // This requirement comes from SurfaceTexture.setDefaultBufferSize() + // documentation. + final Surface surface = getSurface(); + if (surface != null) { + mRenderCallback.onNativeWindowChanged(surface); + } + } + } + + @Override + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) { + if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()"); setSurface(null); + destroySwapChain(); + return true; } - void setSurface(Surface surface) { + @Override + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { } + + + private void setSurface(@Nullable Surface surface) { if (surface == null) { if (mSurface != null) { mSurface.release(); @@ -236,6 +398,10 @@ void setSurface(Surface surface) { } mSurface = surface; } + + private Surface getSurface() { + return mSurface; + } } /** @@ -279,6 +445,9 @@ public RendererCallback getRenderCallback() { * {@link #attachTo(TextureView)}, or {@link #attachTo(SurfaceHolder)}. */ public void detach() { + if (mRenderSurface != null) { + mRenderSurface.detach(); + } destroySwapChain(); mNativeWindow = null; mRenderSurface = null; @@ -286,7 +455,6 @@ public void detach() { /** * Checks whether we are ready to render into the attached surface. - * * Using OpenGL ES when this returns true, will result in drawing commands being lost, * HOWEVER, GLES state will be preserved. This is useful to initialize the engine. * @@ -331,7 +499,6 @@ public boolean isOpaque() { /** * Controls whether the render target (SurfaceView or TextureView) is opaque or not. * The render target is considered opaque by default. - * * Must be called before calling {@link #attachTo(SurfaceView)}, {@link #attachTo(TextureView)}, * or {@link #attachTo(SurfaceHolder)}. * @@ -354,10 +521,8 @@ public boolean isMediaOverlay() { * positioned above other surfaces but below the activity's surface. This property * only has an effect when used in combination with {@link #setOpaque(boolean) setOpaque(false)} * and does not affect TextureView targets. - * * Must be called before calling {@link #attachTo(SurfaceView)} * or {@link #attachTo(TextureView)}. - * * Has no effect when using {@link #attachTo(SurfaceHolder)}. * * @param overlay Indicates whether the render target should be rendered below the activity's @@ -373,12 +538,11 @@ public void setMediaOverlay(boolean overlay) { * the options set on this UiHelper. */ public long getSwapChainFlags() { - return isOpaque() ? SwapChain.CONFIG_DEFAULT : SwapChain.CONFIG_TRANSPARENT; + return isOpaque() ? SwapChainFlags.CONFIG_DEFAULT : SwapChainFlags.CONFIG_TRANSPARENT; } /** * Associate UiHelper with a SurfaceView. - * * As soon as SurfaceView is ready (i.e. has a Surface), we'll create the * EGL resources needed, and call user callbacks if needed. */ @@ -393,163 +557,32 @@ public void attachTo(@NonNull SurfaceView view) { view.setZOrderOnTop(translucent); } - int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; - view.getHolder().setFormat(format); - + view.getHolder().setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT); mRenderSurface = new SurfaceViewHandler(view); - - final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(SurfaceHolder holder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); - createSwapChain(holder.getSurface()); - } - - @Override - public void surfaceChanged( - SurfaceHolder holder, int format, int width, int height) { - // Note: this is always called at least once after surfaceCreated() - if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); - mRenderCallback.onResized(width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); - destroySwapChain(); - } - }; - - SurfaceHolder holder = view.getHolder(); - holder.addCallback(callback); - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - holder.setFixedSize(mDesiredWidth, mDesiredHeight); - } - - // in case the SurfaceView's surface already existed - final Surface surface = holder.getSurface(); - if (surface != null && surface.isValid()) { - callback.surfaceCreated(holder); - callback.surfaceChanged(holder, format, - holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); - } } } /** * Associate UiHelper with a TextureView. - * * As soon as TextureView is ready (i.e. has a buffer), we'll create the * EGL resources needed, and call user callbacks if needed. */ public void attachTo(@NonNull TextureView view) { if (attach(view)) { view.setOpaque(isOpaque()); - mRenderSurface = new TextureViewHandler(view); - - TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() { - @Override - public void onSurfaceTextureAvailable( - SurfaceTexture surfaceTexture, int width, int height) { - if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); - } - } - - Surface surface = new Surface(surfaceTexture); - TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface; - textureViewHandler.setSurface(surface); - - createSwapChain(surface); - - // Call this the first time because onSurfaceTextureSizeChanged() - // isn't called at initialization time - mRenderCallback.onResized(width, height); - } - - @Override - public void onSurfaceTextureSizeChanged( - SurfaceTexture surfaceTexture, int width, int height) { - if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()"); - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); - mRenderCallback.onResized(mDesiredWidth, mDesiredHeight); - } else { - mRenderCallback.onResized(width, height); - } - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { - if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()"); - destroySwapChain(); - return true; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { } - }; - - view.setSurfaceTextureListener(listener); - - // in case the View's SurfaceTexture already existed - if (view.isAvailable()) { - SurfaceTexture surfaceTexture = view.getSurfaceTexture(); - listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight); - } } } /** * Associate UiHelper with a SurfaceHolder. - * * As soon as a Surface is created, we'll create the * EGL resources needed, and call user callbacks if needed. */ public void attachTo(@NonNull SurfaceHolder holder) { if (attach(holder)) { - int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; - holder.setFormat(format); - + holder.setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT); mRenderSurface = new SurfaceHolderHandler(holder); - - final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(SurfaceHolder surfaceHolder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); - createSwapChain(holder.getSurface()); - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - // Note: this is always called at least once after surfaceCreated() - if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); - mRenderCallback.onResized(width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder surfaceHolder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); - destroySwapChain(); - } - }; - - holder.addCallback(callback); - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - holder.setFixedSize(mDesiredWidth, mDesiredHeight); - } - - // in case the SurfaceHolder's surface already existed - final Surface surface = holder.getSurface(); - if (surface != null && surface.isValid()) { - callback.surfaceCreated(holder); - callback.surfaceChanged(holder, format, - holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); - } } } @@ -560,6 +593,10 @@ private boolean attach(@NonNull Object nativeWindow) { // nothing to do return false; } + if (mRenderSurface != null) { + mRenderSurface.detach(); + mRenderSurface = null; + } destroySwapChain(); } mNativeWindow = nativeWindow; @@ -567,15 +604,16 @@ private boolean attach(@NonNull Object nativeWindow) { } private void createSwapChain(@NonNull Surface surface) { - mRenderCallback.onNativeWindowChanged(surface); + if (mRenderCallback != null) { + mRenderCallback.onNativeWindowChanged(surface); + } mHasSwapChain = true; } private void destroySwapChain() { - if (mRenderSurface != null) { - mRenderSurface.detach(); + if (mRenderCallback != null) { + mRenderCallback.onDetachedFromSurface(); } - mRenderCallback.onDetachedFromSurface(); mHasSwapChain = false; } } diff --git a/android/filament-utils-android/CMakeLists.txt b/android/filament-utils-android/CMakeLists.txt index 92c4a319b34..6c77cfa2b32 100644 --- a/android/filament-utils-android/CMakeLists.txt +++ b/android/filament-utils-android/CMakeLists.txt @@ -31,6 +31,7 @@ set_target_properties(iblprefilter PROPERTIES IMPORTED_LOCATION ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilament-iblprefilter.a) set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.map") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384") add_library(filament-utils-jni SHARED src/main/cpp/AutomationEngine.cpp diff --git a/android/filament-utils-android/build.gradle b/android/filament-utils-android/build.gradle index 64bd0c0bbd0..d8a7a1741e6 100644 --- a/android/filament-utils-android/build.gradle +++ b/android/filament-utils-android/build.gradle @@ -1,4 +1,7 @@ apply plugin: 'kotlin-android' +kotlin { + jvmToolchain(versions.jdk) +} android { namespace 'com.google.android.filament.utils' @@ -9,9 +12,6 @@ android { } } - defaultConfig { - missingDimensionStrategy 'functionality', 'full' - } packagingOptions { // No need to package up the following shared libs, which arise as a side effect of our // externalNativeBuild dependencies. When clients pick and choose from project-level gradle @@ -21,16 +21,11 @@ android { excludes += ['lib/*/libfilament-jni.so', 'lib/*/libgltfio-jni.so'] } } -} -configurations.all { config -> - // Hack to preserve the version of the dependencies - if (!config.name.endsWith('Publication')) { - resolutionStrategy { - dependencySubstitution { - substitute(module("com.google.android.filament:gltfio-android:${VERSION_NAME}")).with(project(":gltfio-android")) - substitute(module("com.google.android.filament:gltfio-android-lite:${VERSION_NAME}")).with(project(":gltfio-android")) - } + publishing { + singleVariant("release") { + withSourcesJar() + withJavadocJar() } } } @@ -43,7 +38,7 @@ dependencies { implementation deps.coroutines.android api project(':filament-android') - api module("com.google.android.filament:gltfio-android:${VERSION_NAME}") + api project(':gltfio-android') } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') diff --git a/android/filament-utils-android/src/main/cpp/AutomationEngine.cpp b/android/filament-utils-android/src/main/cpp/AutomationEngine.cpp index 44eb5ed6b18..e55a9605ee8 100644 --- a/android/filament-utils-android/src/main/cpp/AutomationEngine.cpp +++ b/android/filament-utils-android/src/main/cpp/AutomationEngine.cpp @@ -166,6 +166,8 @@ Java_com_google_android_filament_utils_AutomationEngine_nGetViewerOptions(JNIEnv const jfieldID cameraAperture = env->GetFieldID(klass, "cameraAperture", "F"); const jfieldID cameraSpeed = env->GetFieldID(klass, "cameraSpeed", "F"); const jfieldID cameraISO = env->GetFieldID(klass, "cameraISO", "F"); + const jfieldID cameraNear = env->GetFieldID(klass, "cameraNear", "F"); + const jfieldID cameraFar = env->GetFieldID(klass, "cameraFar", "F"); const jfieldID groundShadowStrength = env->GetFieldID(klass, "groundShadowStrength", "F"); const jfieldID groundPlaneEnabled = env->GetFieldID(klass, "groundPlaneEnabled", "Z"); const jfieldID skyboxEnabled = env->GetFieldID(klass, "skyboxEnabled", "Z"); @@ -177,6 +179,8 @@ Java_com_google_android_filament_utils_AutomationEngine_nGetViewerOptions(JNIEnv env->SetFloatField(result, cameraAperture, options.cameraAperture); env->SetFloatField(result, cameraSpeed, options.cameraSpeed); env->SetFloatField(result, cameraISO, options.cameraISO); + env->SetFloatField(result, cameraNear, options.cameraNear); + env->SetFloatField(result, cameraFar, options.cameraFar); env->SetFloatField(result, groundShadowStrength, options.groundShadowStrength); env->SetBooleanField(result, groundPlaneEnabled, options.groundPlaneEnabled); env->SetBooleanField(result, skyboxEnabled, options.skyboxEnabled); diff --git a/android/filament-utils-android/src/main/cpp/Manipulator.cpp b/android/filament-utils-android/src/main/cpp/Manipulator.cpp index 22d471def24..84ac111e759 100644 --- a/android/filament-utils-android/src/main/cpp/Manipulator.cpp +++ b/android/filament-utils-android/src/main/cpp/Manipulator.cpp @@ -125,6 +125,11 @@ extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBu builder->groundPlane(a, b, c, d); } +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderPanning(JNIEnv*, jclass, jlong nativeBuilder, jboolean enabled) { + Builder* builder = (Builder*) nativeBuilder; + builder->panning(enabled); +} + extern "C" JNIEXPORT long Java_com_google_android_filament_utils_Manipulator_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder, jint mode) { Builder* builder = (Builder*) nativeBuilder; return (jlong) builder->build((Mode) mode); diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/AutomationEngine.java b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/AutomationEngine.java index 93987d6c36a..daa1da4274b 100644 --- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/AutomationEngine.java +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/AutomationEngine.java @@ -97,6 +97,8 @@ public static class ViewerOptions { public float cameraAperture = 16.0f; public float cameraSpeed = 125.0f; public float cameraISO = 100.0f; + public float cameraNear = 0.1f; + public float cameraFar = 100.0f; public float groundShadowStrength = 0.75f; public boolean groundPlaneEnabled = false; public boolean skyboxEnabled = true; diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Half.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Half.kt new file mode 100644 index 00000000000..f985d58b6ff --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Half.kt @@ -0,0 +1,1171 @@ +/* + * Copyright (C) 2022 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Operators +, *, / based on http://half.sourceforge.net/ by Christian Rau +// and licensed under MIT + +@file:Suppress("NOTHING_TO_INLINE") + +package com.google.android.filament.utils + +import com.google.android.filament.utils.Half.Companion.POSITIVE_INFINITY +import com.google.android.filament.utils.Half.Companion.POSITIVE_ZERO + +import kotlin.jvm.JvmInline + +/** + * Converts the specified double-precision float value into a + * half-precision float value. The following special cases are handled: + * + * - If the input is NaN (see [Double.isNaN]), the returned value is [Half.NaN] + * - If the input is [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY], + * the returned value is respectively [Half.POSITIVE_INFINITY] or [Half.NEGATIVE_INFINITY] + * - If the input is 0 (positive or negative), the returned value is [Half.POSITIVE_ZERO] + * or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_VALUE], the returned value is flushed to + * [Half.POSITIVE_ZERO] or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_NORMAL], the returned value is a denormal + * half-precision float + * - Otherwise, the returned value is rounded to the nearest representable + * half-precision float value + * + * @param value The double-precision float value to convert to half-precision + * @return A half-precision float value + */ +fun Half(value: Double) = Half(floatToHalf(value.toFloat())) + +/** + * Converts this double-precision float value into a half-precision float value. + * The following special cases are handled: + * + * - If the input is NaN (see [Double.isNaN]), the returned value is [Half.NaN] + * - If the input is [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY], + * the returned value is respectively [Half.POSITIVE_INFINITY] or [Half.NEGATIVE_INFINITY] + * - If the input is 0 (positive or negative), the returned value is [Half.POSITIVE_ZERO] + * or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_VALUE], the returned value is flushed to + * [Half.POSITIVE_ZERO] or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_NORMAL], the returned value is a denormal + * half-precision float + * - Otherwise, the returned value is rounded to the nearest representable + * half-precision float value + * + * @return A half-precision float value + */ +fun Double.toHalf() = Half(floatToHalf(toFloat())) + +/** + * Converts this double-precision float value into a half-precision float value. + * The following special cases are handled: + * + * - If the input is NaN (see [Double.isNaN]), the returned value is [Half.NaN] + * - If the input is [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY], + * the returned value is respectively [Half.POSITIVE_INFINITY] or [Half.NEGATIVE_INFINITY] + * - If the input is 0 (positive or negative), the returned value is [Half.POSITIVE_ZERO] + * or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_VALUE], the returned value is flushed to + * [Half.POSITIVE_ZERO] or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_NORMAL], the returned value is a denormal + * half-precision float + * - Otherwise, the returned value is rounded to the nearest representable + * half-precision float value + * + * @return A half-precision float value + */ +val Double.h: Half + get() = Half(floatToHalf(toFloat())) + +/** + * Converts the specified single-precision float value into a + * half-precision float value. The following special cases are handled: + * + * - If the input is NaN (see [Float.isNaN]), the returned value is [Half.NaN] + * - If the input is [Float.POSITIVE_INFINITY] or [Float.NEGATIVE_INFINITY], + * the returned value is respectively [Half.POSITIVE_INFINITY] or [Half.NEGATIVE_INFINITY] + * - If the input is 0 (positive or negative), the returned value is [Half.POSITIVE_ZERO] + * or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_VALUE], the returned value is flushed to + * [Half.POSITIVE_ZERO] or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_NORMAL], the returned value is a denormal + * half-precision float + * - Otherwise, the returned value is rounded to the nearest representable + * half-precision float value + * + * @param value The single-precision float value to convert to half-precision + * @return A half-precision float value + */ +fun Half(value: Float) = Half(floatToHalf(value)) + +/** + * Converts this single-precision float value into a half-precision float value. + * The following special cases are handled: + * + * - If the input is NaN (see [Float.isNaN]), the returned value is [Half.NaN] + * - If the input is [Float.POSITIVE_INFINITY] or [Float.NEGATIVE_INFINITY], + * the returned value is respectively [Half.POSITIVE_INFINITY] or [Half.NEGATIVE_INFINITY] + * - If the input is 0 (positive or negative), the returned value is [Half.POSITIVE_ZERO] + * or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_VALUE], the returned value is flushed to + * [Half.POSITIVE_ZERO] or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_NORMAL], the returned value is a denormal + * half-precision float + * - Otherwise, the returned value is rounded to the nearest representable + * half-precision float value + * + * @return A half-precision float value + */ +fun Float.toHalf() = Half(floatToHalf(this)) + +/** + * Converts this single-precision float value into a half-precision float value. + * The following special cases are handled: + * + * - If the input is NaN (see [Float.isNaN]), the returned value is [Half.NaN] + * - If the input is [Float.POSITIVE_INFINITY] or [Float.NEGATIVE_INFINITY], + * the returned value is respectively [Half.POSITIVE_INFINITY] or [Half.NEGATIVE_INFINITY] + * - If the input is 0 (positive or negative), the returned value is [Half.POSITIVE_ZERO] + * or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_VALUE], the returned value is flushed to + * [Half.POSITIVE_ZERO] or [Half.NEGATIVE_ZERO] + * - If the input is less than [Half.MIN_NORMAL], the returned value is a denormal + * half-precision float + * - Otherwise, the returned value is rounded to the nearest representable + * half-precision float value + * + * @return A half-precision float value + */ +val Float.h: Half + get() = Half(floatToHalf(this)) + +/** + * Returns the half-precision float value represented by the specified string. + * The string is converted to a half-precision float value as if by the + * [String.toFloat()] method. + * + * Calling this function is equivalent to calling: + * ``` + * Half(value.toFloat()) + * ``` + * + * @param value A string to be converted to a {@code Half} + * @throws NumberFormatException if the string does not contain a parsable number + * + * @see String.toFloat + */ +fun Half(value: String) = Half(floatToHalf(value.toFloat())) + +/** + * Returns the half-precision float value represented by the specified string. + * The string is converted to a half-precision float value as if by the + * [String.toFloat()] method. + * + * Calling this function is equivalent to calling: + * ``` + * Half(value.toFloat()) + * ``` + * + * @throws NumberFormatException if the string does not contain a parsable number + * + * @see String.toFloat + */ +fun String.toHalf() = Half(floatToHalf(toFloat())) + +/** + * The [Half] class is a wrapper and a utility class to manipulate half-precision 16-bit + * [IEEE 754](https://en.wikipedia.org/wiki/Half-precision_floating-point_format) + * floating point data types (also called fp16 or binary16). A half-precision float can be + * created from or converted to single-precision floats, and is stored in a short data type. + * + * The IEEE 754 standard specifies an fp16 as having the following format: + * - Sign bit: 1 bit + * - Exponent width: 5 bits + * - Significand: 10 bits + * + * The format is laid out as follows: + * ``` + * 1 11111 1111111111 + * ^ --^-- -----^---- + * sign | |_______ significand + * | + * -- exponent + * ``` + * + * Half-precision floating points can be useful to save memory and/or + * bandwidth at the expense of range and precision when compared to single-precision + * floating points (fp32). + * + * To help you decide whether fp16 is the right storage type for you need, please + * refer to the table below that shows the available precision throughout the range of + * possible values. The _precision_ column indicates the step size between two + * consecutive numbers in a specific part of the range. + * + * | Range start | Precision | + * |------------------|----------------------| + * | 0 | 1 ⁄ 16,777,216 | + * | 1 ⁄ 16,384 | 1 ⁄ 16,777,216 | + * | 1 ⁄ 8,192 | 1 ⁄ 8,388,608 | + * | 1 ⁄ 4,096 | 1 ⁄ 4,194,304 | + * | 1 ⁄ 2,048 | 1 ⁄ 2,097,152 | + * | 1 ⁄ 1,024 | 1 ⁄ 1,048,576 | + * | 1 ⁄ 512 | 1 ⁄ 524,288 | + * | 1 ⁄ 256 | 1 ⁄ 262,144 | + * | 1 ⁄ 128 | 1 ⁄ 131,072 | + * | 1 ⁄ 64 | 1 ⁄ 65,536 | + * | 1 ⁄ 32 | 1 ⁄ 32,768 | + * | 1 ⁄ 16 | 1 ⁄ 16,384 | + * | 1 ⁄ 8 | 1 ⁄ 8,192 | + * | 1 ⁄ 4 | 1 ⁄ 4,096 | + * | 1 ⁄ 2 | 1 ⁄ 2,048 | + * | 1 | 1 ⁄ 1,024 | + * | 2 | 1 ⁄ 512 | + * | 4 | 1 ⁄ 256 | + * | 8 | 1 ⁄ 128 | + * | 16 | 1 ⁄ 64 | + * | 32 | 1 ⁄ 32 | + * | 64 | 1 ⁄ 16 | + * | 128 | 1 ⁄ 8 | + * | 256 | 1 ⁄ 4 | + * | 512 | 1 ⁄ 2 | + * | 1,024 | 1 | + * | 2,048 | 2 | + * | 4,096 | 4 | + * | 8,192 | 8 | + * | 16,384 | 16 | + * | 32,768 | 32 | + * + * This table shows that numbers higher than 1024 lose all fractional precision. + */ +@JvmInline +value class Half(private val v: UShort) : Comparable
Builder
object for chaining calls
+ */
+ @NonNull
+ public Builder panning(Boolean enabled) {
+ nBuilderPanning(mNativeBuilder, enabled);
+ return this;
+ }
+
/**
* Creates and returns the Manipulator
object.
*
@@ -483,6 +494,7 @@ public void jumpToBookmark(Bookmark bookmark) {
private static native void nBuilderFlightPanSpeed(long nativeBuilder, float x, float y);
private static native void nBuilderFlightMoveDamping(long nativeBuilder, float damping);
private static native void nBuilderGroundPlane(long nativeBuilder, float a, float b, float c, float d);
+ private static native void nBuilderPanning(long nativeBuilder, Boolean enabled);
private static native long nBuilderBuild(long nativeBuilder, int mode);
private static native void nDestroyManipulator(long nativeManip);
diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Matrix.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Matrix.kt
index 1f6a79c9bd1..ade4428135f 100644
--- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Matrix.kt
+++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Matrix.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:Suppress("unused")
+@file:Suppress("NOTHING_TO_INLINE", "unused")
package com.google.android.filament.utils
@@ -24,8 +24,16 @@ enum class MatrixColumn {
X, Y, Z, W
}
-enum class RotationsOrder {
- XYZ, XZY, YXZ, YZX, ZXY, ZYX
+enum class RotationsOrder(
+ val yaw: VectorComponent,
+ val pitch: VectorComponent,
+ val roll: VectorComponent) {
+ XYZ(VectorComponent.X, VectorComponent.Y, VectorComponent.Z),
+ XZY(VectorComponent.X, VectorComponent.Z, VectorComponent.Y),
+ YXZ(VectorComponent.Y, VectorComponent.X, VectorComponent.Z),
+ YZX(VectorComponent.Y, VectorComponent.Z, VectorComponent.X),
+ ZXY(VectorComponent.Z, VectorComponent.X, VectorComponent.Y),
+ ZYX(VectorComponent.Z, VectorComponent.Y, VectorComponent.X);
}
data class Mat2(
@@ -77,6 +85,12 @@ data class Mat2(
operator fun minus(v: Float) = Mat2(x - v, y - v)
operator fun times(v: Float) = Mat2(x * v, y * v)
operator fun div(v: Float) = Mat2(x / v, y / v)
+ inline fun compareTo(v: Float, delta: Float = 0.0f) = Mat2(
+ x.compareTo(v, delta),
+ y.compareTo(v, delta)
+ )
+
+ inline fun equals(v: Float, delta: Float = 0.0f) = x.equals(v, delta) && y.equals(v, delta)
operator fun times(m: Mat2) = Mat2(
Float2(
@@ -89,12 +103,18 @@ data class Mat2(
)
)
+ inline fun compareTo(m: Mat2, delta: Float = 0.0f) = Mat2(
+ x.compareTo(m.x, delta),
+ y.compareTo(m.y, delta)
+ )
+
+ inline fun equals(m: Mat2, delta: Float = 0.0f) = x.equals(m.x, delta) && y.equals(m.y, delta)
+
operator fun times(v: Float2) = Float2(
x.x * v.x + y.x * v.y,
x.y * v.x + y.y * v.y,
)
-
fun toFloatArray() = floatArrayOf(
x.x, y.x,
x.y, y.y
@@ -106,7 +126,6 @@ data class Mat2(
|${x.y} ${y.y}|
""".trimIndent()
}
-
}
data class Mat3(
@@ -162,6 +181,14 @@ data class Mat3(
operator fun minus(v: Float) = Mat3(x - v, y - v, z - v)
operator fun times(v: Float) = Mat3(x * v, y * v, z * v)
operator fun div(v: Float) = Mat3(x / v, y / v, z / v)
+ inline fun compareTo(v: Float, delta: Float = 0.0f) = Mat3(
+ x.compareTo(v, delta),
+ y.compareTo(v, delta),
+ z.compareTo(v, delta)
+ )
+
+ inline fun equals(v: Float, delta: Float = 0.0f) =
+ x.equals(v, delta) && y.equals(v, delta) && z.equals(v, delta)
operator fun times(m: Mat3) = Mat3(
Float3(
@@ -181,6 +208,15 @@ data class Mat3(
)
)
+ inline fun compareTo(m: Mat3, delta: Float = 0.0f) = Mat3(
+ x.compareTo(m.x, delta),
+ y.compareTo(m.y, delta),
+ z.compareTo(m.z, delta)
+ )
+
+ inline fun equals(m: Mat3, delta: Float = 0.0f) =
+ x.equals(m.x, delta) && y.equals(m.y, delta) && z.equals(m.z, delta)
+
operator fun times(v: Float3) = Float3(
x.x * v.x + y.x * v.y + z.x * v.z,
x.y * v.x + y.y * v.y + z.y * v.z,
@@ -212,6 +248,7 @@ data class Mat4(
constructor(m: Mat4) : this(m.x.copy(), m.y.copy(), m.z.copy(), m.w.copy())
companion object {
+
fun of(vararg a: Float): Mat4 {
require(a.size >= 16)
return Mat4(
@@ -302,6 +339,15 @@ data class Mat4(
operator fun minus(v: Float) = Mat4(x - v, y - v, z - v, w - v)
operator fun times(v: Float) = Mat4(x * v, y * v, z * v, w * v)
operator fun div(v: Float) = Mat4(x / v, y / v, z / v, w / v)
+ inline fun compareTo(v: Float, delta: Float = 0.0f) = Mat4(
+ x.compareTo(v, delta),
+ y.compareTo(v, delta),
+ z.compareTo(v, delta),
+ w.compareTo(v, delta)
+ )
+
+ inline fun equals(v: Float, delta: Float = 0.0f) =
+ x.equals(v, delta) && y.equals(v, delta) && z.equals(v, delta) && w.equals(v, delta)
operator fun times(m: Mat4) = Mat4(
Float4(
@@ -330,6 +376,16 @@ data class Mat4(
)
)
+ inline fun compareTo(m: Mat4, delta: Float = 0.0f) = Mat4(
+ x.compareTo(m.x, delta),
+ y.compareTo(m.y, delta),
+ z.compareTo(m.z, delta),
+ w.compareTo(m.w, delta)
+ )
+
+ inline fun equals(m: Mat4, delta: Float = 0.0f) =
+ x.equals(m.x, delta) && y.equals(m.y, delta) && z.equals(m.z, delta) && w.equals(m.w, delta)
+
operator fun times(v: Float4) = Float4(
x.x * v.x + y.x * v.y + z.x * v.z+ w.x * v.w,
x.y * v.x + y.y * v.y + z.y * v.z+ w.y * v.w,
@@ -337,6 +393,26 @@ data class Mat4(
x.w * v.x + y.w * v.y + z.w * v.z+ w.w * v.w
)
+ /**
+ * Get the Euler angles in degrees from this rotation Matrix
+ *
+ * Don't forget to extract the rotation with [rotation] if this is a transposed matrix
+ *
+ * @param order The order in which to apply rotations.
+ * Default is [RotationsOrder.ZYX] which means that the object will first be rotated around its Z
+ * axis, then its Y axis and finally its X axis.
+ *
+ * @see eulerAngles
+ */
+ fun toEulerAngles(order: RotationsOrder = RotationsOrder.ZYX) = eulerAngles(this, order)
+
+ /**
+ * Get the [Quaternion] from this rotation Matrix
+ *
+ * Don't forget to extract the rotation with [rotation] if this is a transposed matrix
+ *
+ * @see quaternion
+ */
fun toQuaternion() = quaternion(this)
fun toFloatArray() = floatArrayOf(
@@ -356,6 +432,78 @@ data class Mat4(
}
}
+inline fun equal(a: Mat2, b: Float, delta: Float = 0.0f) = Bool2(
+ a.x.equals(b, delta),
+ a.y.equals(b, delta)
+)
+
+inline fun equal(a: Mat2, b: Mat2, delta: Float = 0.0f) = Bool2(
+ a.x.equals(b.x, delta),
+ a.y.equals(b.y, delta)
+)
+
+inline fun notEqual(a: Mat2, b: Float, delta: Float = 0.0f) = Bool2(
+ !a.x.equals(b, delta),
+ !a.y.equals(b, delta)
+)
+
+inline fun notEqual(a: Mat2, b: Mat2, delta: Float = 0.0f) = Bool2(
+ !a.x.equals(b.x, delta),
+ !a.y.equals(b.y, delta)
+)
+
+inline fun equal(a: Mat3, b: Float, delta: Float = 0.0f) = Bool3(
+ a.x.equals(b, delta),
+ a.y.equals(b, delta),
+ a.z.equals(b, delta)
+)
+
+inline fun equal(a: Mat3, b: Mat3, delta: Float = 0.0f) = Bool3(
+ a.x.equals(b.x, delta),
+ a.y.equals(b.y, delta),
+ a.z.equals(b.z, delta)
+)
+
+inline fun notEqual(a: Mat3, b: Float, delta: Float = 0.0f) = Bool3(
+ !a.x.equals(b, delta),
+ !a.y.equals(b, delta),
+ !a.z.equals(b, delta)
+)
+
+inline fun notEqual(a: Mat3, b: Mat3, delta: Float = 0.0f) = Bool3(
+ !a.x.equals(b.x, delta),
+ !a.y.equals(b.y, delta),
+ !a.z.equals(b.z, delta)
+)
+
+inline fun equal(a: Mat4, b: Float, delta: Float = 0.0f) = Bool4(
+ a.x.equals(b, delta),
+ a.y.equals(b, delta),
+ a.z.equals(b, delta),
+ a.w.equals(b, delta)
+)
+
+inline fun equal(a: Mat4, b: Mat4, delta: Float = 0.0f) = Bool4(
+ a.x.equals(b.x, delta),
+ a.y.equals(b.y, delta),
+ a.z.equals(b.z, delta),
+ a.w.equals(b.w, delta)
+)
+
+inline fun notEqual(a: Mat4, b: Float, delta: Float = 0.0f) = Bool4(
+ !a.x.equals(b, delta),
+ !a.y.equals(b, delta),
+ !a.z.equals(b, delta),
+ !a.w.equals(b, delta)
+)
+
+inline fun notEqual(a: Mat4, b: Mat4, delta: Float = 0.0f) = Bool4(
+ !a.x.equals(b.x, delta),
+ !a.y.equals(b.y, delta),
+ !a.z.equals(b.z, delta),
+ !a.w.equals(b.w, delta)
+)
+
fun transpose(m: Mat2) = Mat2(
Float2(m.x.x, m.y.x),
Float2(m.x.y, m.y.y)
@@ -494,14 +642,7 @@ fun rotation(m: Mat4) = Mat4(normalize(m.right), normalize(m.up), normalize(m.fo
*/
fun rotation(d: Float3, order: RotationsOrder = RotationsOrder.ZYX): Mat4 {
val r = transform(d, ::radians)
- return when(order) {
- RotationsOrder.XZY -> rotation(r.x, r.z, r.y)
- RotationsOrder.XYZ -> rotation(r.x, r.y, r.z)
- RotationsOrder.YXZ -> rotation(r.y, r.x, r.z)
- RotationsOrder.YZX -> rotation(r.y, r.z, r.x)
- RotationsOrder.ZYX -> rotation(r.z, r.y, r.x)
- RotationsOrder.ZXY -> rotation(r.z, r.x, r.y)
- }
+ return rotation(r[order.yaw], r[order.pitch], r[order.roll], order)
}
/**
@@ -599,13 +740,93 @@ fun rotation(quaternion: Quaternion): Mat4 {
Float4(
2.0f * (n.x * n.z + n.y * n.w),
2.0f * (n.y * n.z - n.x * n.w),
- 1.0f - 2.0f * (n.x * n.x + n.y * n.y),
+ 1.0f - 2.0f * (n.x * n.x + n.y * n.y)
)
)
}
/**
- * Extract Quaternion rotation from a Matrix
+ * Get the Euler angles in degrees from a rotation Matrix
+ *
+ * @param m The rotation matrix.
+ * Don't forget to extract the rotation with [rotation] if it's transposed
+ * @param order The order in which to apply rotations.
+ * Default is [RotationsOrder.ZYX] which means that the object will first be rotated around its Z
+ * axis, then its Y axis and finally its X axis.
+ */
+fun eulerAngles(m: Mat4, order: RotationsOrder = RotationsOrder.ZYX): Float3 {
+ // We need to more simplify this with RotationsOrder VectorComponents mapped to MatrixColumn
+ return transform(Float3().apply {
+ when (order) {
+ RotationsOrder.XYZ -> {
+ this[order.pitch] = asin(clamp(m.z.x, -1.0f, 1.0f))
+ if (abs(m.z.x) < 0.9999999f) {
+ this[order.yaw] = atan2(-m.z.y, m.z.z)
+ this[order.roll] = atan2(-m.y.x, m.x.x)
+ } else {
+ this[order.yaw] = atan2(m.y.z, m.y.y)
+ this[order.roll] = 0.0f
+ }
+ }
+ RotationsOrder.XZY -> {
+ this[order.pitch] = asin(-clamp(m.y.x, -1.0f, 1.0f))
+ if (abs(m.y.x) < 0.9999999f) {
+ this[order.yaw] = atan2(m.y.z, m.y.y)
+ this[order.roll] = atan2(m.z.x, m.x.x)
+ } else {
+ this[order.yaw] = atan2(-m.z.y, m.z.z)
+ this[order.roll] = 0.0f
+ }
+ }
+ RotationsOrder.YXZ -> {
+ this[order.pitch] = asin(-clamp(m.z.y, -1.0f, 1.0f))
+ if (abs(m.z.y) < 0.9999999f) {
+ this[order.yaw] = atan2(m.z.x, m.z.z)
+ this[order.roll] = atan2(m.x.y, m.y.y)
+ } else {
+ this[order.yaw] = atan2(-m.x.z, m.x.x)
+ this[order.roll] = 0.0f
+ }
+ }
+ RotationsOrder.YZX -> {
+ this[order.pitch] = asin(clamp(m.x.y, -1.0f, 1.0f))
+ if (abs(m.x.y) < 0.9999999f) {
+ this[order.roll] = atan2(-m.z.y, m.y.y)
+ this[order.yaw] = atan2(-m.x.z, m.x.x)
+ } else {
+ this[order.roll] = 0.0f
+ this[order.yaw] = atan2(m.z.x, m.z.z)
+ }
+ }
+ RotationsOrder.ZXY -> {
+ this[order.pitch] = asin(clamp(m.y.z, -1.0f, 1.0f))
+ if (abs(m.y.z) < 0.9999999f) {
+ this[order.roll] = atan2(-m.x.z, m.z.z)
+ this[order.yaw] = atan2(-m.y.x, m.y.y)
+ } else {
+ this[order.roll] = 0.0f
+ this[order.yaw] = atan2(m.x.y, m.x.x)
+ }
+ }
+ RotationsOrder.ZYX -> {
+ this[order.pitch] = asin(-clamp(m.x.z, -1.0f, 1.0f))
+ if (abs(m.x.z) < 0.9999999f) {
+ this[order.roll] = atan2(m.y.z, m.z.z)
+ this[order.yaw] = atan2(m.x.y, m.x.x)
+ } else {
+ this[order.roll] = 0.0f
+ this[order.yaw] = atan2(-m.y.x, m.y.y)
+ }
+ }
+ }
+ }, ::degrees)
+}
+
+/**
+ * Get the [Quaternion] from a rotation Matrix
+ *
+ * @param m The rotation matrix.
+ * Don't forget to extract the rotation with [rotation] if it's transposed
*/
fun quaternion(m: Mat4): Quaternion {
val trace = m.x.x + m.y.y + m.z.z
@@ -673,9 +894,14 @@ fun perspective(fov: Float, ratio: Float, near: Float, far: Float): Mat4 {
}
fun ortho(l: Float, r: Float, b: Float, t: Float, n: Float, f: Float) = Mat4(
- Float4(x = 2.0f / (r - 1.0f)),
- Float4(y = 2.0f / (t - b)),
- Float4(z = -2.0f / (f - n)),
- Float4(-(r + l) / (r - l), -(t + b) / (t - b), -(f + n) / (f - n), 1.0f)
+ Float4(x = 2.0f / (r - l)),
+ Float4(y = 2.0f / (t - b)),
+ Float4(z = -2.0f / (f - n)),
+ Float4(
+ -(r + l) / (r - l),
+ -(t + b) / (t - b),
+ -(f + n) / (f - n),
+ 1.0f
+ )
)
diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt
index 8e5939fe39b..2f11466b494 100644
--- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt
+++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt
@@ -27,8 +27,8 @@ import com.google.android.filament.gltfio.*
import kotlinx.coroutines.*
import java.nio.Buffer
-private const val kNearPlane = 0.05 // 5 cm
-private const val kFarPlane = 1000.0 // 1 km
+private const val kNearPlane = 0.05f // 5 cm
+private const val kFarPlane = 1000.0f // 1 km
private const val kAperture = 16f
private const val kShutterSpeed = 1f / 125f
private const val kSensitivity = 100f
@@ -80,6 +80,18 @@ class ModelViewer(
updateCameraProjection()
}
+ var cameraNear = kNearPlane
+ set(value) {
+ field = value
+ updateCameraProjection()
+ }
+
+ var cameraFar = kFarPlane
+ set(value) {
+ field = value
+ updateCameraProjection()
+ }
+
val scene: Scene
val view: View
val camera: Camera
@@ -179,7 +191,7 @@ class ModelViewer(
asset = assetLoader.createAsset(buffer)
asset?.let { asset ->
resourceLoader.asyncBeginLoad(asset)
- animator = asset.getInstance().animator
+ animator = asset.instance.animator
asset.releaseSourceData()
}
}
@@ -202,7 +214,7 @@ class ModelViewer(
resourceLoader.addResourceData(uri, resourceBuffer)
}
resourceLoader.asyncBeginLoad(asset)
- animator = asset.getInstance().animator
+ animator = asset.instance.animator
asset.releaseSourceData()
}
}
@@ -299,7 +311,7 @@ class ModelViewer(
var count = 0
val popRenderables = { count = asset.popRenderables(readyRenderables); count != 0 }
while (popRenderables()) {
- for (i in 0..count - 1) {
+ for (i in 0 until count) {
val ri = rcm.getInstance(readyRenderables[i])
rcm.setScreenSpaceContactShadows(ri, true)
}
@@ -359,7 +371,7 @@ class ModelViewer(
resourceLoader.addResourceData(uri, buffer)
}
resourceLoader.asyncBeginLoad(asset)
- animator = asset.getInstance().animator
+ animator = asset.instance.animator
asset.releaseSourceData()
}
}
@@ -368,7 +380,8 @@ class ModelViewer(
val width = view.viewport.width
val height = view.viewport.height
val aspect = width.toDouble() / height.toDouble()
- camera.setLensProjection(cameraFocalLength.toDouble(), aspect, kNearPlane, kFarPlane)
+ camera.setLensProjection(cameraFocalLength.toDouble(), aspect,
+ cameraNear.toDouble(), cameraFar.toDouble())
}
inner class SurfaceCallback : UiHelper.RendererCallback {
@@ -392,9 +405,19 @@ class ModelViewer(
view.viewport = Viewport(0, 0, width, height)
cameraManipulator.setViewport(width, height)
updateCameraProjection()
+ synchronizePendingFrames(engine)
}
}
+ private fun synchronizePendingFrames(engine: Engine) {
+ // Wait for all pending frames to be processed before returning. This is to
+ // avoid a race between the surface being resized before pending frames are
+ // rendered into it.
+ val fence = engine.createFence()
+ fence.wait(Fence.Mode.FLUSH, Fence.WAIT_FOR_EVER)
+ engine.destroyFence(fence)
+ }
+
companion object {
private val kDefaultObjectPosition = Float3(0.0f, 0.0f, -4.0f)
}
diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Quaternion.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Quaternion.kt
index da57c8b7f31..bb694db214f 100644
--- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Quaternion.kt
+++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Quaternion.kt
@@ -33,9 +33,9 @@ data class Quaternion(
var x: Float = 0.0f,
var y: Float = 0.0f,
var z: Float = 0.0f,
- var w: Float = 0.0f) {
+ var w: Float = 1.0f) {
- constructor(v: Float3, w: Float = 0.0f) : this(v.x, v.y, v.z, w)
+ constructor(v: Float3, w: Float = 1.0f) : this(v.x, v.y, v.z, w)
constructor(v: Float4) : this(v.x, v.y, v.z, v.w)
constructor(q: Quaternion) : this(q.x, q.y, q.z, q.w)
@@ -52,42 +52,84 @@ data class Quaternion(
}
/**
- * Construct a Quaternion from Euler angles using YPR around ZYX respectively
+ * Construct a Quaternion from Euler angles using YPR around a specified order
*
- * The Euler angles are applied in ZYX order.
- * i.e: a vector is first rotated about X (roll) then Y (pitch) and then Z (yaw).
+ * Uses intrinsic Tait-Bryan angles. This means that rotations are performed with respect to
+ * the local coordinate system.
+ * That is, for order 'XYZ', the rotation is first around the X axis (which is the same as
+ * the world-X axis), then around local-Y (which may now be different from the world
+ * Y-axis), then local-Z (which may be different from the world Z-axis)
*
* @param d Per axis Euler angles in degrees
+ * Yaw, pitch, roll (YPR) are taken accordingly to the rotations order input.
+ * @param order The order in which to apply rotations.
+ * Default is [RotationsOrder.ZYX] which means that the object will first be rotated around
+ * its Z axis, then its Y axis and finally its X axis.
*/
- fun fromEuler(d: Float3): Quaternion {
+ fun fromEuler(d: Float3, order: RotationsOrder = RotationsOrder.ZYX): Quaternion {
val r = transform(d, ::radians)
- return fromEulerZYX(r.z, r.y, r.x)
+ return fromEuler(r[order.yaw], r[order.pitch], r[order.roll], order)
}
/**
- * Construct a Quaternion from Euler angles using YPR around ZYX respectively
+ * Construct a Quaternion from Euler yaw, pitch, roll around a specified order.
*
- * The Euler angles are applied in ZYX order.
- * i.e: a vector is first rotated about X (roll) then Y (pitch) and then Z (yaw).
- *
- * @param roll about X axis in radians
- * @param pitch about Y axis in radians
- * @param yaw about Z axis in radians
+ * @param roll about 1st rotation axis in radians. Z in case of ZYX order
+ * @param pitch about 2nd rotation axis in radians. Y in case of ZYX order
+ * @param yaw about 3rd rotation axis in radians. X in case of ZYX order
+ * @param order The order in which to apply rotations.
+ * Default is [RotationsOrder.ZYX] which means that the object will first be rotated around its Z
+ * axis, then its Y axis and finally its X axis.
*/
- fun fromEulerZYX(yaw: Float = 0.0f, pitch: Float = 0.0f, roll: Float = 0.0f): Quaternion {
- val cy = cos(yaw * 0.5f)
- val sy = sin(yaw * 0.5f)
- val cp = cos(pitch * 0.5f)
- val sp = sin(pitch * 0.5f)
- val cr = cos(roll * 0.5f)
- val sr = sin(roll * 0.5f)
-
- return Quaternion(
- sr * cp * cy - cr * sp * sy,
- cr * sp * cy + sr * cp * sy,
- cr * cp * sy - sr * sp * cy,
- cr * cp * cy + sr * sp * sy
- )
+ fun fromEuler(
+ yaw: Float = 0.0f,
+ pitch: Float = 0.0f,
+ roll: Float = 0.0f,
+ order: RotationsOrder = RotationsOrder.ZYX
+ ): Quaternion {
+ val c1 = cos(yaw * 0.5f)
+ val s1 = sin(yaw * 0.5f)
+ val c2 = cos(pitch * 0.5f)
+ val s2 = sin(pitch * 0.5f)
+ val c3 = cos(roll * 0.5f)
+ val s3 = sin(roll * 0.5f)
+ return when (order) {
+ RotationsOrder.XZY -> Quaternion(
+ s1 * c2 * c3 - c1 * s2 * s3,
+ c1 * c2 * s3 - s1 * s2 * c3,
+ s1 * c2 * s3 + c1 * s2 * c3,
+ s1 * s2 * s3 + c1 * c2 * c3)
+ RotationsOrder.XYZ -> Quaternion(
+ s1 * c2 * c3 + s2 * s3 * c1,
+ s2 * c1 * c3 - s1 * s3 * c2,
+ s1 * s2 * c3 + s3 * c1 * c2,
+ c1 * c2 * c3 - s1 * s2 * s3
+ )
+ RotationsOrder.YXZ -> Quaternion(
+ s1 * c2 * s3 + c1 * s2 * c3,
+ s1 * c2 * c3 - c1 * s2 * s3,
+ c1 * c2 * s3 - s1 * s2 * c3,
+ s1 * s2 * s3 + c1 * c2 * c3
+ )
+ RotationsOrder.YZX -> Quaternion(
+ s1 * s2 * c3 + c1 * c2 * s3,
+ s1 * c2 * c3 + c1 * s2 * s3,
+ c1 * s2 * c3 - s1 * c2 * s3,
+ c1 * c2 * c3 - s1 * s2 * s3
+ )
+ RotationsOrder.ZYX -> Quaternion(
+ c1 * c2 * s3 - s1 * s2 * c3,
+ s1 * c2 * s3 + c1 * s2 * c3,
+ s1 * c2 * c3 - c1 * s2 * s3,
+ s1 * s2 * s3 + c1 * c2 * c3
+ )
+ RotationsOrder.ZXY -> Quaternion(
+ c1 * s2 * c3 - s1 * c2 * s3,
+ s1 * s2 * c3 + c1 * c2 * s3,
+ s1 * c2 * c3 + c1 * s2 * s3,
+ c1 * c2 * c3 - s1 * s2 * s3
+ )
+ }
}
}
@@ -222,16 +264,44 @@ data class Quaternion(
inline operator fun minus(v: Float) = Quaternion(x - v, y - v, z - v, w - v)
inline operator fun times(v: Float) = Quaternion(x * v, y * v, z * v, w * v)
inline operator fun div(v: Float) = Quaternion(x / v, y / v, z / v, w / v)
+ inline fun compareTo(v: Float, delta: Float = 0.0f) = Float4(
+ x.compareTo(v, delta),
+ y.compareTo(v, delta),
+ z.compareTo(v, delta),
+ w.compareTo(v, delta)
+ )
+
+ inline fun equals(v: Float, delta: Float = 0.0f) = Bool4(
+ x.equals(v, delta),
+ y.equals(v, delta),
+ z.equals(v, delta),
+ w.equals(v, delta)
+ )
inline operator fun times(v: Float3) = (this * Quaternion(v, 0.0f) * inverse(this)).xyz
inline operator fun plus(q: Quaternion) = Quaternion(x + q.x, y + q.y, z + q.z, w + q.w)
inline operator fun minus(q: Quaternion) = Quaternion(x - q.x, y - q.y, z - q.z, w - q.w)
inline operator fun times(q: Quaternion) = Quaternion(
- w * q.x + x * q.w + y * q.z - z * q.y,
- w * q.y - x * q.z + y * q.w + z * q.x,
- w * q.z + x * q.y - y * q.x + z * q.w,
- w * q.w - x * q.x - y * q.y - z * q.z)
+ w * q.x + x * q.w + y * q.z - z * q.y,
+ w * q.y - x * q.z + y * q.w + z * q.x,
+ w * q.z + x * q.y - y * q.x + z * q.w,
+ w * q.w - x * q.x - y * q.y - z * q.z
+ )
+
+ inline fun compareTo(v: Float4, delta: Float = 0.0f) = Float4(
+ x.compareTo(v.x, delta),
+ y.compareTo(v.y, delta),
+ z.compareTo(v.z, delta),
+ w.compareTo(v.w, delta)
+ )
+
+ inline fun equals(v: Float4, delta: Float = 0.0f) = Bool4(
+ x.equals(v.x, delta),
+ y.equals(v.y, delta),
+ z.equals(v.z, delta),
+ w.equals(v.w, delta)
+ )
inline fun transform(block: (Float) -> Float): Quaternion {
x = block(x)
@@ -253,6 +323,103 @@ inline operator fun Float.minus(q: Quaternion) = Quaternion(this - q.x, this - q
inline operator fun Float.times(q: Quaternion) = Quaternion(this * q.x, this * q.y, this * q.z, this * q.w)
inline operator fun Float.div(q: Quaternion) = Quaternion(this / q.x, this / q.y, this / q.z, this / q.w)
+inline fun lessThan(a: Quaternion, b: Float) = Bool4(
+ a.x < b,
+ a.y < b,
+ a.z < b,
+ a.w < b
+)
+
+inline fun lessThan(a: Quaternion, b: Quaternion) = Bool4(
+ a.x < b.x,
+ a.y < b.y,
+ a.z < b.z,
+ a.w < b.w
+)
+
+inline fun lessThanEqual(a: Quaternion, b: Float) = Bool4(
+ a.x <= b,
+ a.y <= b,
+ a.z <= b,
+ a.w <= b
+)
+
+inline fun lessThanEqual(a: Quaternion, b: Quaternion) = Bool4(
+ a.x <= b.x,
+ a.y <= b.y,
+ a.z <= b.z,
+ a.w <= b.w
+)
+
+inline fun greaterThan(a: Quaternion, b: Float) = Bool4(
+ a.x > b,
+ a.y > b,
+ a.z > b,
+ a.w > b
+)
+
+inline fun greaterThan(a: Quaternion, b: Quaternion) = Bool4(
+ a.x > b.y,
+ a.y > b.y,
+ a.z > b.z,
+ a.w > b.w
+)
+
+inline fun greaterThanEqual(a: Quaternion, b: Float) = Bool4(
+ a.x >= b,
+ a.y >= b,
+ a.z >= b,
+ a.w >= b
+)
+
+inline fun greaterThanEqual(a: Quaternion, b: Quaternion) = Bool4(
+ a.x >= b.x,
+ a.y >= b.y,
+ a.z >= b.z,
+ a.w >= b.w
+)
+
+inline fun equal(a: Quaternion, b: Float, delta: Float = 0.0f) = Bool4(
+ a.x.equals(b, delta),
+ a.y.equals(b, delta),
+ a.z.equals(b, delta),
+ a.w.equals(b, delta)
+)
+
+inline fun equal(a: Quaternion, b: Quaternion, delta: Float = 0.0f) = Bool4(
+ a.x.equals(b.x, delta),
+ a.y.equals(b.y, delta),
+ a.z.equals(b.z, delta),
+ a.w.equals(b.w, delta)
+)
+
+inline fun notEqual(a: Quaternion, b: Float, delta: Float = 0.0f) = Bool4(
+ !a.x.equals(b, delta),
+ !a.y.equals(b, delta),
+ !a.z.equals(b, delta),
+ !a.w.equals(b, delta)
+)
+
+inline fun notEqual(a: Quaternion, b: Quaternion, delta: Float = 0.0f) = Bool4(
+ !a.x.equals(b.x, delta),
+ !a.y.equals(b.y, delta),
+ !a.z.equals(b.z, delta),
+ !a.w.equals(b.w, delta)
+)
+
+inline infix fun Quaternion.lt(b: Float) = Bool4(x < b, y < b, z < b, w < b)
+inline infix fun Quaternion.lt(b: Float4) = Bool4(x < b.x, y < b.y, z < b.z, w < b.w)
+inline infix fun Quaternion.lte(b: Float) = Bool4(x <= b, y <= b, z <= b, w <= b)
+inline infix fun Quaternion.lte(b: Float4) = Bool4(x <= b.x, y <= b.y, z <= b.z, w <= b.w)
+inline infix fun Quaternion.gt(b: Float) = Bool4(x > b, y > b, z > b, w > b)
+inline infix fun Quaternion.gt(b: Float4) = Bool4(x > b.x, y > b.y, z > b.z, w > b.w)
+inline infix fun Quaternion.gte(b: Float) = Bool4(x >= b, y >= b, z >= b, w >= b)
+inline infix fun Quaternion.gte(b: Float4) = Bool4(x >= b.x, y >= b.y, z >= b.z, w >= b.w)
+inline infix fun Quaternion.eq(b: Float) = Bool4(x == b, y == b, z == b, w == b)
+inline infix fun Quaternion.eq(b: Float4) = Bool4(x == b.x, y == b.y, z == b.z, w == b.w)
+inline infix fun Quaternion.neq(b: Float) = Bool4(x != b, y != b, z != b, w != b)
+inline infix fun Quaternion.neq(b: Float4) = Bool4(x != b.x, y != b.y, z != b.z, w != b.w)
+
inline fun abs(q: Quaternion) = Quaternion(abs(q.x), abs(q.y), abs(q.z), abs(q.w))
inline fun length(q: Quaternion) = sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w)
inline fun length2(q: Quaternion) = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w
@@ -278,6 +445,10 @@ fun cross(a: Quaternion, b: Quaternion): Quaternion {
return Quaternion(m.x, m.y, m.z, 0.0f)
}
+fun angle(a: Quaternion, b: Quaternion): Float {
+ return 2.0f * acos(abs(clamp(dot(a, b), -1.0f, 1.0f)))
+}
+
/**
* Spherical linear interpolation between two given orientations
*
@@ -287,36 +458,38 @@ fun cross(a: Quaternion, b: Quaternion): Quaternion {
* @param a The beginning value
* @param b The ending value
* @param t The ratio between the two floats
- * @param valueEps Prevent blowing up when slerping between two quaternions that are very near each
- * other. Linear interpolation (lerp) is returned in this case.
+ * @param dotThreshold If the quaternion dot product is greater than this value
+ * (i.e. the quaternions are very close to each other), then the quaternions are
+ * linearly interpolated instead of spherically interpolated.
*
* @return Interpolated value between the two floats
*/
-fun slerp(a: Quaternion, b: Quaternion, t: Float, valueEps: Float = 0.0000000001f): Quaternion {
+fun slerp(a: Quaternion, b: Quaternion, t: Float, dotThreshold: Float = 0.9995f): Quaternion {
// could also be computed as: pow(q * inverse(p), t) * p;
- val d = dot(a, b)
- val absd = abs(d)
- // Prevent blowing up when slerping between two quaternions that are very near each other.
- if ((1.0f - absd) < valueEps) {
- return normalize(lerp(if (d < 0.0f) -a else a, b, t))
+ var dot = dot(a, b)
+ var b1 = b
+
+ // If the dot product is negative, then the interpolation won't follow the shortest angular path
+ // between the two quaterions. In this case, invert the end quaternion to produce an equivalent
+ // rotation that will give us the path we want.
+ if (dot < 0.0f) {
+ dot = -dot
+ b1 = -b
}
- val npq = sqrt(dot(a, a) * dot(b, b)) // ||p|| * ||q||
- val acos = acos(clamp(absd / npq, -1.0f, 1.0f))
- val acos0 = acos * (1.0f - t)
- val acos1 = acos * t
- val sina = sin(acos)
- if (sina < valueEps) {
- return normalize(lerp(a, b, t))
+
+ // Prevent blowing up when slerping between two quaternions that are very near each other.
+ return if (dot < dotThreshold) {
+ val angle = acos(dot)
+ val s = sin(angle)
+ a * sin((1.0f - t) * angle) / s + b1 * sin(t * angle) / s
+ } else {
+ // If the angle is too small, use linear interpolation
+ nlerp(a, b1, t)
}
- val isina = 1.0f / sina
- val s0 = sin(acos0) * isina
- val s1 = sin(acos1) * isina
- // ensure we're taking the "short" side
- return normalize(s0 * a + (if (d < 0.0f) -s1 else (s1)) * b)
}
fun lerp(a: Quaternion, b: Quaternion, t: Float): Quaternion {
- return ((1 - t) * a) + (t * b)
+ return ((1.0f - t) * a) + (t * b)
}
fun nlerp(a: Quaternion, b: Quaternion, t: Float): Quaternion {
@@ -324,19 +497,12 @@ fun nlerp(a: Quaternion, b: Quaternion, t: Float): Quaternion {
}
/**
- * Convert a Quaternion to Euler angles using YPR around ZYX respectively
+ * Convert a Quaternion to Euler angles
*
- * The Euler angles are applied in ZYX order
+ * @param order The order in which to apply rotations.
+ * Default is [RotationsOrder.ZYX] which means that the object will first be rotated around its Z
+ * axis, then its Y axis and finally its X axis.
*/
-fun eulerAngles(q: Quaternion): Float3 {
- val nq = normalize(q)
- return Float3(
- // roll (x-axis rotation)
- degrees(atan2(2.0f * (nq.y * nq.z + nq.w * nq.x),
- nq.w * nq.w - nq.x * nq.x - nq.y * nq.y + nq.z * nq.z)),
- // pitch (y-axis rotation)
- degrees(asin(-2.0f * (nq.x * nq.z - nq.w * nq.y))),
- // yaw (z-axis rotation)
- degrees(atan2(2.0f * (nq.x * nq.y + nq.w * nq.z),
- nq.w * nq.w + nq.x * nq.x - nq.y * nq.y - nq.z * nq.z)))
+fun eulerAngles(q: Quaternion, order: RotationsOrder = RotationsOrder.ZYX): Float3 {
+ return eulerAngles(rotation(q), order)
}
diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Scalar.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Scalar.kt
index a6126aa3d1d..5283b153aaf 100644
--- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Scalar.kt
+++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Scalar.kt
@@ -28,12 +28,21 @@ const val INV_PI = 1.0f / FPI
const val INV_TWO_PI = INV_PI * 0.5f
const val INV_FOUR_PI = INV_PI * 0.25f
-inline fun clamp(x: Float, min: Float, max: Float)= if (x < min) min else (if (x > max) max else x)
+val HALF_ONE = Half(0x3c00.toUShort())
+val HALF_TWO = Half(0x4000.toUShort())
+
+inline fun clamp(x: Float, min: Float, max: Float) = if (x < min) min else (if (x > max) max else x)
+
+inline fun clamp(x: Half, min: Half, max: Half) = if (x < min) min else (if (x > max) max else x)
inline fun saturate(x: Float) = clamp(x, 0.0f, 1.0f)
+inline fun saturate(x: Half) = clamp(x, Half.POSITIVE_ZERO, HALF_ONE)
+
inline fun mix(a: Float, b: Float, x: Float) = a * (1.0f - x) + b * x
+inline fun mix(a: Half, b: Half, x: Half) = a * (HALF_ONE - x) + b * x
+
inline fun degrees(v: Float) = v * (180.0f * INV_PI)
inline fun radians(v: Float) = v * (FPI / 180.0f)
@@ -42,4 +51,6 @@ inline fun fract(v: Float) = v % 1
inline fun sqr(v: Float) = v * v
+inline fun sqr(v: Half) = v * v
+
inline fun pow(x: Float, y: Float) = (x.toDouble().pow(y.toDouble())).toFloat()
diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/TextureLoader.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/TextureLoader.kt
index 5d9dc297a71..aeea9665533 100644
--- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/TextureLoader.kt
+++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/TextureLoader.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.filament.textured
+package com.google.android.filament.utils
import android.content.res.Resources
import android.graphics.Bitmap
diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Vector.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Vector.kt
index 23e59de1fc2..d43a87fc5ac 100644
--- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Vector.kt
+++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Vector.kt
@@ -22,6 +22,8 @@ import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt
+import kotlin.math.acos
+import kotlin.math.absoluteValue
enum class VectorComponent {
X, Y, Z, W,
@@ -124,11 +126,23 @@ data class Float2(var x: Float = 0.0f, var y: Float = 0.0f) {
inline operator fun minus(v: Float) = Float2(x - v, y - v)
inline operator fun times(v: Float) = Float2(x * v, y * v)
inline operator fun div(v: Float) = Float2(x / v, y / v)
+ inline fun compareTo(v: Float, delta: Float = 0.0f) = Float2(
+ x.compareTo(v, delta),
+ y.compareTo(v, delta)
+ )
+
+ inline fun equals(v: Float, delta: Float = 0.0f) = x.equals(v, delta) && y.equals(v, delta)
inline operator fun plus(v: Float2) = Float2(x + v.x, y + v.y)
inline operator fun minus(v: Float2) = Float2(x - v.x, y - v.y)
inline operator fun times(v: Float2) = Float2(x * v.x, y * v.y)
inline operator fun div(v: Float2) = Float2(x / v.x, y / v.y)
+ inline fun compareTo(v: Float2, delta: Float = 0.0f) = Float2(
+ x.compareTo(v.x, delta),
+ y.compareTo(v.y, delta)
+ )
+
+ inline fun equals(v: Float2, delta: Float = 0.0f) = x.equals(v.x, delta) && y.equals(v.y, delta)
inline fun transform(block: (Float) -> Float): Float2 {
x = block(x)
@@ -291,6 +305,14 @@ data class Float3(var x: Float = 0.0f, var y: Float = 0.0f, var z: Float = 0.0f)
inline operator fun minus(v: Float) = Float3(x - v, y - v, z - v)
inline operator fun times(v: Float) = Float3(x * v, y * v, z * v)
inline operator fun div(v: Float) = Float3(x / v, y / v, z / v)
+ inline fun compareTo(v: Float, delta: Float = 0.0f) = Float3(
+ x.compareTo(v, delta),
+ y.compareTo(v, delta),
+ z.compareTo(v, delta)
+ )
+
+ inline fun equals(v: Float, delta: Float = 0.0f) =
+ x.equals(v, delta) && y.equals(v, delta) && z.equals(v, delta)
inline operator fun plus(v: Float2) = Float3(x + v.x, y + v.y, z)
inline operator fun minus(v: Float2) = Float3(x - v.x, y - v.y, z)
@@ -301,6 +323,14 @@ data class Float3(var x: Float = 0.0f, var y: Float = 0.0f, var z: Float = 0.0f)
inline operator fun minus(v: Float3) = Float3(x - v.x, y - v.y, z - v.z)
inline operator fun times(v: Float3) = Float3(x * v.x, y * v.y, z * v.z)
inline operator fun div(v: Float3) = Float3(x / v.x, y / v.y, z / v.z)
+ inline fun compareTo(v: Float3, delta: Float = 0.0f) = Float3(
+ x.compareTo(v.x, delta),
+ y.compareTo(v.y, delta),
+ z.compareTo(v.z, delta)
+ )
+
+ inline fun equals(v: Float3, delta: Float = 0.0f) =
+ x.equals(v.x, delta) && y.equals(v.y, delta) && z.equals(v.z, delta)
inline fun transform(block: (Float) -> Float): Float3 {
x = block(x)
@@ -534,6 +564,15 @@ data class Float4(
inline operator fun minus(v: Float) = Float4(x - v, y - v, z - v, w - v)
inline operator fun times(v: Float) = Float4(x * v, y * v, z * v, w * v)
inline operator fun div(v: Float) = Float4(x / v, y / v, z / v, w / v)
+ inline fun compareTo(v: Float, delta: Float = 0.0f) = Float4(
+ x.compareTo(v, delta),
+ y.compareTo(v, delta),
+ z.compareTo(v, delta),
+ w.compareTo(v, delta)
+ )
+
+ inline fun equals(v: Float, delta: Float = 0.0f) =
+ x.equals(v, delta) && y.equals(v, delta) && z.equals(v, delta) && w.equals(v, delta)
inline operator fun plus(v: Float2) = Float4(x + v.x, y + v.y, z, w)
inline operator fun minus(v: Float2) = Float4(x - v.x, y - v.y, z, w)
@@ -549,6 +588,15 @@ data class Float4(
inline operator fun minus(v: Float4) = Float4(x - v.x, y - v.y, z - v.z, w - v.w)
inline operator fun times(v: Float4) = Float4(x * v.x, y * v.y, z * v.z, w * v.w)
inline operator fun div(v: Float4) = Float4(x / v.x, y / v.y, z / v.z, w / v.w)
+ inline fun compareTo(v: Float4, delta: Float = 0.0f) = Float4(
+ x.compareTo(v.x, delta),
+ y.compareTo(v.y, delta),
+ z.compareTo(v.z, delta),
+ w.compareTo(v.w, delta)
+ )
+
+ inline fun equals(v: Float4, delta: Float = 0.0f) =
+ x.equals(v.x, delta) && y.equals(v.y, delta) && z.equals(v.z, delta) && w.equals(v.w, delta)
inline fun transform(block: (Float) -> Float): Float4 {
x = block(x)
@@ -566,6 +614,12 @@ inline operator fun Float.minus(v: Float2) = Float2(this - v.x, this - v.y)
inline operator fun Float.times(v: Float2) = Float2(this * v.x, this * v.y)
inline operator fun Float.div(v: Float2) = Float2(this / v.x, this / v.y)
+inline fun Float.compareTo(v: Float, delta: Float): Float = when {
+ equals(v, delta) -> 0.0f
+ else -> compareTo(v).toFloat()
+}
+
+inline fun Float.equals(v: Float, delta: Float) = (this - v).absoluteValue < delta
inline fun abs(v: Float2) = Float2(abs(v.x), abs(v.y))
inline fun length(v: Float2) = sqrt(v.x * v.x + v.y * v.y)
inline fun length2(v: Float2) = v.x * v.x + v.y * v.y
@@ -583,6 +637,11 @@ fun refract(i: Float2, n: Float2, eta: Float): Float2 {
return if (k < 0.0f) Float2(0.0f) else eta * i - (eta * d + sqrt(k)) * n
}
+inline fun angle(a: Float2, b: Float2): Float {
+ val l = length(a) * length(b)
+ return if (l == 0.0f) 0.0f else acos(clamp(dot(a, b) / l, -1.0f, 1.0f))
+}
+
inline fun clamp(v: Float2, min: Float, max: Float): Float2 {
return Float2(
clamp(v.x, min, max),
@@ -626,10 +685,25 @@ inline fun greaterThan(a: Float2, b: Float) = Bool2(a.x > b, a.y > b)
inline fun greaterThan(a: Float2, b: Float2) = Bool2(a.x > b.y, a.y > b.y)
inline fun greaterThanEqual(a: Float2, b: Float) = Bool2(a.x >= b, a.y >= b)
inline fun greaterThanEqual(a: Float2, b: Float2) = Bool2(a.x >= b.x, a.y >= b.y)
-inline fun equal(a: Float2, b: Float) = Bool2(a.x == b, a.y == b)
-inline fun equal(a: Float2, b: Float2) = Bool2(a.x == b.x, a.y == b.y)
-inline fun notEqual(a: Float2, b: Float) = Bool2(a.x != b, a.y != b)
-inline fun notEqual(a: Float2, b: Float2) = Bool2(a.x != b.x, a.y != b.y)
+inline fun equal(a: Float2, b: Float, delta: Float = 0.0f) = Bool2(
+ a.x.equals(b, delta),
+ a.y.equals(b, delta)
+)
+
+inline fun equal(a: Float2, b: Float2, delta: Float = 0.0f) = Bool2(
+ a.x.equals(b.x, delta),
+ a.y.equals(b.y, delta)
+)
+
+inline fun notEqual(a: Float2, b: Float, delta: Float = 0.0f) = Bool2(
+ !a.x.equals(b, delta),
+ !a.y.equals(b, delta)
+)
+
+inline fun notEqual(a: Float2, b: Float2, delta: Float = 0.0f) = Bool2(
+ !a.x.equals(b.x, delta),
+ !a.y.equals(b.y, delta)
+)
inline infix fun Float2.lt(b: Float) = Bool2(x < b, y < b)
inline infix fun Float2.lt(b: Float2) = Bool2(x < b.x, y < b.y)
@@ -675,6 +749,11 @@ fun refract(i: Float3, n: Float3, eta: Float): Float3 {
return if (k < 0.0f) Float3(0.0f) else eta * i - (eta * d + sqrt(k)) * n
}
+inline fun angle(a: Float3, b: Float3): Float {
+ val l = length(a) * length(b)
+ return if (l == 0.0f) 0.0f else acos(clamp(dot(a, b) / l, -1.0f, 1.0f))
+}
+
inline fun clamp(v: Float3, min: Float, max: Float): Float3 {
return Float3(
clamp(v.x, min, max),
@@ -722,10 +801,29 @@ inline fun greaterThan(a: Float3, b: Float) = Bool3(a.x > b, a.y > b, a.z > b)
inline fun greaterThan(a: Float3, b: Float3) = Bool3(a.x > b.y, a.y > b.y, a.z > b.z)
inline fun greaterThanEqual(a: Float3, b: Float) = Bool3(a.x >= b, a.y >= b, a.z >= b)
inline fun greaterThanEqual(a: Float3, b: Float3) = Bool3(a.x >= b.x, a.y >= b.y, a.z >= b.z)
-inline fun equal(a: Float3, b: Float) = Bool3(a.x == b, a.y == b, a.z == b)
-inline fun equal(a: Float3, b: Float3) = Bool3(a.x == b.x, a.y == b.y, a.z == b.z)
-inline fun notEqual(a: Float3, b: Float) = Bool3(a.x != b, a.y != b, a.z != b)
-inline fun notEqual(a: Float3, b: Float3) = Bool3(a.x != b.x, a.y != b.y, a.z != b.z)
+inline fun equal(a: Float3, b: Float, delta: Float = 0.0f) = Bool3(
+ a.x.equals(b, delta),
+ a.y.equals(b, delta),
+ a.z.equals(b, delta)
+)
+
+inline fun equal(a: Float3, b: Float3, delta: Float = 0.0f) = Bool3(
+ a.x.equals(b.x, delta),
+ a.y.equals(b.y, delta),
+ a.z.equals(b.z, delta)
+)
+
+inline fun notEqual(a: Float3, b: Float, delta: Float = 0.0f) = Bool3(
+ !a.x.equals(b, delta),
+ !a.y.equals(b, delta),
+ !a.z.equals(b, delta)
+)
+
+inline fun notEqual(a: Float3, b: Float3, delta: Float = 0.0f) = Bool3(
+ !a.x.equals(b.x, delta),
+ !a.y.equals(b.y, delta),
+ !a.z.equals(b.z, delta)
+)
inline infix fun Float3.lt(b: Float) = Bool3(x < b, y < b, z < b)
inline infix fun Float3.lt(b: Float3) = Bool3(x < b.x, y < b.y, z < b.z)
@@ -807,17 +905,44 @@ inline fun transform(v: Float4, block: (Float) -> Float) = v.copy().transform(bl
inline fun lessThan(a: Float4, b: Float) = Bool4(a.x < b, a.y < b, a.z < b, a.w < b)
inline fun lessThan(a: Float4, b: Float4) = Bool4(a.x < b.x, a.y < b.y, a.z < b.z, a.w < b.w)
inline fun lessThanEqual(a: Float4, b: Float) = Bool4(a.x <= b, a.y <= b, a.z <= b, a.w <= b)
-inline fun lessThanEqual(a: Float4, b: Float4) = Bool4(a.x <= b.x, a.y <= b.y, a.z <= b.z, a.w <= b.w)
+inline fun lessThanEqual(a: Float4, b: Float4) =
+ Bool4(a.x <= b.x, a.y <= b.y, a.z <= b.z, a.w <= b.w)
+
inline fun greaterThan(a: Float4, b: Float) = Bool4(a.x > b, a.y > b, a.z > b, a.w > b)
inline fun greaterThan(a: Float4, b: Float4) = Bool4(a.x > b.y, a.y > b.y, a.z > b.z, a.w > b.w)
inline fun greaterThanEqual(a: Float4, b: Float) = Bool4(a.x >= b, a.y >= b, a.z >= b, a.w >= b)
-inline fun greaterThanEqual(a: Float4, b: Float4) = Bool4(a.x >= b.x, a.y >= b.y, a.z >= b.z, a.w >= b.w)
-inline fun equal(a: Float4, b: Float) = Bool4(a.x == b, a.y == b, a.z == b, a.w == b)
-inline fun equal(a: Float4, b: Float4) = Bool4(a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w)
-inline fun notEqual(a: Float4, b: Float) = Bool4(a.x != b, a.y != b, a.z != b, a.w != b)
-inline fun notEqual(a: Float4, b: Float4) = Bool4(a.x != b.x, a.y != b.y, a.z != b.z, a.w != b.w)
-
-inline infix fun Float4.lt(b: Float) = Bool4(x < b, y < b, z < b, a < b)
+inline fun greaterThanEqual(a: Float4, b: Float4) =
+ Bool4(a.x >= b.x, a.y >= b.y, a.z >= b.z, a.w >= b.w)
+
+inline fun equal(a: Float4, b: Float, delta: Float = 0.0f) = Bool4(
+ a.x.equals(b, delta),
+ a.y.equals(b, delta),
+ a.z.equals(b, delta),
+ a.w.equals(b, delta)
+)
+
+inline fun equal(a: Float4, b: Float4, delta: Float = 0.0f) = Bool4(
+ a.x.equals(b.x, delta),
+ a.y.equals(b.y, delta),
+ a.z.equals(b.z, delta),
+ a.w.equals(b.w, delta)
+)
+
+inline fun notEqual(a: Float4, b: Float, delta: Float = 0.0f) = Bool4(
+ !a.x.equals(b, delta),
+ !a.y.equals(b, delta),
+ !a.z.equals(b, delta),
+ !a.w.equals(b, delta)
+)
+
+inline fun notEqual(a: Float4, b: Float4, delta: Float = 0.0f) = Bool4(
+ !a.x.equals(b.x, delta),
+ !a.y.equals(b.y, delta),
+ !a.z.equals(b.z, delta),
+ !a.w.equals(b.w, delta)
+)
+
+inline infix fun Float4.lt(b: Float) = Bool4(x < b, y < b, z < b, w < b)
inline infix fun Float4.lt(b: Float4) = Bool4(x < b.x, y < b.y, z < b.z, w < b.w)
inline infix fun Float4.lte(b: Float) = Bool4(x <= b, y <= b, z <= b, w <= b)
inline infix fun Float4.lte(b: Float4) = Bool4(x <= b.x, y <= b.y, z <= b.z, w <= b.w)
@@ -1277,3 +1402,753 @@ data class Bool4(
set(index4, v)
}
}
+
+data class Half2(var x: Half = Half.POSITIVE_ZERO, var y: Half = Half.POSITIVE_ZERO) {
+ constructor(v: Half) : this(v, v)
+ constructor(v: Half2) : this(v.x, v.y)
+
+ inline var r: Half
+ get() = x
+ set(value) {
+ x = value
+ }
+ inline var g: Half
+ get() = y
+ set(value) {
+ y = value
+ }
+
+ inline var s: Half
+ get() = x
+ set(value) {
+ x = value
+ }
+ inline var t: Half
+ get() = y
+ set(value) {
+ y = value
+ }
+
+ inline var xy: Half2
+ get() = Half2(x, y)
+ set(value) {
+ x = value.x
+ y = value.y
+ }
+ inline var rg: Half2
+ get() = Half2(x, y)
+ set(value) {
+ x = value.x
+ y = value.y
+ }
+ inline var st: Half2
+ get() = Half2(x, y)
+ set(value) {
+ x = value.x
+ y = value.y
+ }
+
+ operator fun get(index: VectorComponent) = when (index) {
+ VectorComponent.X, VectorComponent.R, VectorComponent.S -> x
+ VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y
+ else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T")
+ }
+
+ operator fun get(index1: VectorComponent, index2: VectorComponent): Half2 {
+ return Half2(get(index1), get(index2))
+ }
+
+ operator fun get(index: Int) = when (index) {
+ 0 -> x
+ 1 -> y
+ else -> throw IllegalArgumentException("index must be in 0..1")
+ }
+
+ operator fun get(index1: Int, index2: Int) = Half2(get(index1), get(index2))
+
+ inline operator fun invoke(index: Int) = get(index - 1)
+
+ operator fun set(index: Int, v: Half) = when (index) {
+ 0 -> x = v
+ 1 -> y = v
+ else -> throw IllegalArgumentException("index must be in 0..1")
+ }
+
+ operator fun set(index1: Int, index2: Int, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ }
+
+ operator fun set(index: VectorComponent, v: Half) = when (index) {
+ VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v
+ VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v
+ else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T")
+ }
+
+ operator fun set(index1: VectorComponent, index2: VectorComponent, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ }
+
+ operator fun unaryMinus() = Half2(-x, -y)
+ operator fun inc() = Half2(x++, y++)
+ operator fun dec() = Half2(x--, y--)
+
+ inline operator fun plus(v: Half) = Half2(x + v, y + v)
+ inline operator fun minus(v: Half) = Half2(x - v, y - v)
+ inline operator fun times(v: Half) = Half2(x * v, y * v)
+ inline operator fun div(v: Half) = Half2(x / v, y / v)
+
+ inline operator fun plus(v: Half2) = Half2(x + v.x, y + v.y)
+ inline operator fun minus(v: Half2) = Half2(x - v.x, y - v.y)
+ inline operator fun times(v: Half2) = Half2(x * v.x, y * v.y)
+ inline operator fun div(v: Half2) = Half2(x / v.x, y / v.y)
+
+ inline fun transform(block: (Half) -> Half): Half2 {
+ x = block(x)
+ y = block(y)
+ return this
+ }
+
+ fun toFloatArray() = floatArrayOf(x.toFloat(), y.toFloat())
+}
+
+data class Half3(
+ var x: Half = Half.POSITIVE_ZERO,
+ var y: Half = Half.POSITIVE_ZERO,
+ var z: Half = Half.POSITIVE_ZERO
+) {
+ constructor(v: Half) : this(v, v, v)
+ constructor(v: Half2, z: Half = Half.POSITIVE_ZERO) : this(v.x, v.y, z)
+ constructor(v: Half3) : this(v.x, v.y, v.z)
+
+ inline var r: Half
+ get() = x
+ set(value) {
+ x = value
+ }
+ inline var g: Half
+ get() = y
+ set(value) {
+ y = value
+ }
+ inline var b: Half
+ get() = z
+ set(value) {
+ z = value
+ }
+
+ inline var s: Half
+ get() = x
+ set(value) {
+ x = value
+ }
+ inline var t: Half
+ get() = y
+ set(value) {
+ y = value
+ }
+ inline var p: Half
+ get() = z
+ set(value) {
+ z = value
+ }
+
+ inline var xy: Half2
+ get() = Half2(x, y)
+ set(value) {
+ x = value.x
+ y = value.y
+ }
+ inline var rg: Half2
+ get() = Half2(x, y)
+ set(value) {
+ x = value.x
+ y = value.y
+ }
+ inline var st: Half2
+ get() = Half2(x, y)
+ set(value) {
+ x = value.x
+ y = value.y
+ }
+
+ inline var rgb: Half3
+ get() = Half3(x, y, z)
+ set(value) {
+ x = value.x
+ y = value.y
+ z = value.z
+ }
+ inline var xyz: Half3
+ get() = Half3(x, y, z)
+ set(value) {
+ x = value.x
+ y = value.y
+ z = value.z
+ }
+ inline var stp: Half3
+ get() = Half3(x, y, z)
+ set(value) {
+ x = value.x
+ y = value.y
+ z = value.z
+ }
+
+ operator fun get(index: VectorComponent) = when (index) {
+ VectorComponent.X, VectorComponent.R, VectorComponent.S -> x
+ VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y
+ VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z
+ else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P")
+ }
+
+ operator fun get(index1: VectorComponent, index2: VectorComponent): Half2 {
+ return Half2(get(index1), get(index2))
+ }
+ operator fun get(
+ index1: VectorComponent, index2: VectorComponent, index3: VectorComponent): Half3 {
+ return Half3(get(index1), get(index2), get(index3))
+ }
+
+ operator fun get(index: Int) = when (index) {
+ 0 -> x
+ 1 -> y
+ 2 -> z
+ else -> throw IllegalArgumentException("index must be in 0..2")
+ }
+
+ operator fun get(index1: Int, index2: Int) = Half2(get(index1), get(index2))
+ operator fun get(index1: Int, index2: Int, index3: Int): Half3 {
+ return Half3(get(index1), get(index2), get(index3))
+ }
+
+ inline operator fun invoke(index: Int) = get(index - 1)
+
+ operator fun set(index: Int, v: Half) = when (index) {
+ 0 -> x = v
+ 1 -> y = v
+ 2 -> z = v
+ else -> throw IllegalArgumentException("index must be in 0..2")
+ }
+
+ operator fun set(index1: Int, index2: Int, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ }
+
+ operator fun set(index1: Int, index2: Int, index3: Int, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ set(index3, v)
+ }
+
+ operator fun set(index: VectorComponent, v: Half) = when (index) {
+ VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v
+ VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v
+ VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v
+ else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P")
+ }
+
+ operator fun set(index1: VectorComponent, index2: VectorComponent, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ }
+
+ operator fun set(
+ index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ set(index3, v)
+ }
+
+ operator fun unaryMinus() = Half3(-x, -y, -z)
+ operator fun inc() = Half3(x++, y++, z++)
+ operator fun dec() = Half3(x--, y--, z--)
+
+ inline operator fun plus(v: Half) = Half3(x + v, y + v, z + v)
+ inline operator fun minus(v: Half) = Half3(x - v, y - v, z - v)
+ inline operator fun times(v: Half) = Half3(x * v, y * v, z * v)
+ inline operator fun div(v: Half) = Half3(x / v, y / v, z / v)
+
+ inline operator fun plus(v: Half2) = Half3(x + v.x, y + v.y, z)
+ inline operator fun minus(v: Half2) = Half3(x - v.x, y - v.y, z)
+ inline operator fun times(v: Half2) = Half3(x * v.x, y * v.y, z)
+ inline operator fun div(v: Half2) = Half3(x / v.x, y / v.y, z)
+
+ inline operator fun plus(v: Half3) = Half3(x + v.x, y + v.y, z + v.z)
+ inline operator fun minus(v: Half3) = Half3(x - v.x, y - v.y, z - v.z)
+ inline operator fun times(v: Half3) = Half3(x * v.x, y * v.y, z * v.z)
+ inline operator fun div(v: Half3) = Half3(x / v.x, y / v.y, z / v.z)
+
+ inline fun transform(block: (Half) -> Half): Half3 {
+ x = block(x)
+ y = block(y)
+ z = block(z)
+ return this
+ }
+
+ fun toFloatArray() = floatArrayOf(x.toFloat(), y.toFloat(), z.toFloat())
+}
+
+data class Half4(
+ var x: Half = Half.POSITIVE_ZERO,
+ var y: Half = Half.POSITIVE_ZERO,
+ var z: Half = Half.POSITIVE_ZERO,
+ var w: Half = Half.POSITIVE_ZERO
+) {
+ constructor(v: Half) : this(v, v, v, v)
+ constructor(v: Half2, z: Half = Half.POSITIVE_ZERO, w: Half = Half.POSITIVE_ZERO) : this(v.x, v.y, z, w)
+ constructor(v: Half3, w: Half = Half.POSITIVE_ZERO) : this(v.x, v.y, v.z, w)
+ constructor(v: Half4) : this(v.x, v.y, v.z, v.w)
+
+ inline var r: Half
+ get() = x
+ set(value) {
+ x = value
+ }
+ inline var g: Half
+ get() = y
+ set(value) {
+ y = value
+ }
+ inline var b: Half
+ get() = z
+ set(value) {
+ z = value
+ }
+ inline var a: Half
+ get() = w
+ set(value) {
+ w = value
+ }
+
+ inline var s: Half
+ get() = x
+ set(value) {
+ x = value
+ }
+ inline var t: Half
+ get() = y
+ set(value) {
+ y = value
+ }
+ inline var p: Half
+ get() = z
+ set(value) {
+ z = value
+ }
+ inline var q: Half
+ get() = w
+ set(value) {
+ w = value
+ }
+
+ inline var xy: Half2
+ get() = Half2(x, y)
+ set(value) {
+ x = value.x
+ y = value.y
+ }
+ inline var rg: Half2
+ get() = Half2(x, y)
+ set(value) {
+ x = value.x
+ y = value.y
+ }
+ inline var st: Half2
+ get() = Half2(x, y)
+ set(value) {
+ x = value.x
+ y = value.y
+ }
+
+ inline var rgb: Half3
+ get() = Half3(x, y, z)
+ set(value) {
+ x = value.x
+ y = value.y
+ z = value.z
+ }
+ inline var xyz: Half3
+ get() = Half3(x, y, z)
+ set(value) {
+ x = value.x
+ y = value.y
+ z = value.z
+ }
+ inline var stp: Half3
+ get() = Half3(x, y, z)
+ set(value) {
+ x = value.x
+ y = value.y
+ z = value.z
+ }
+
+ inline var rgba: Half4
+ get() = Half4(x, y, z, w)
+ set(value) {
+ x = value.x
+ y = value.y
+ z = value.z
+ w = value.w
+ }
+ inline var xyzw: Half4
+ get() = Half4(x, y, z, w)
+ set(value) {
+ x = value.x
+ y = value.y
+ z = value.z
+ w = value.w
+ }
+ inline var stpq: Half4
+ get() = Half4(x, y, z, w)
+ set(value) {
+ x = value.x
+ y = value.y
+ z = value.z
+ w = value.w
+ }
+
+ operator fun get(index: VectorComponent) = when (index) {
+ VectorComponent.X, VectorComponent.R, VectorComponent.S -> x
+ VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y
+ VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z
+ VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w
+ }
+
+ operator fun get(index1: VectorComponent, index2: VectorComponent): Half2 {
+ return Half2(get(index1), get(index2))
+ }
+ operator fun get(
+ index1: VectorComponent,
+ index2: VectorComponent,
+ index3: VectorComponent): Half3 {
+ return Half3(get(index1), get(index2), get(index3))
+ }
+ operator fun get(
+ index1: VectorComponent,
+ index2: VectorComponent,
+ index3: VectorComponent,
+ index4: VectorComponent): Half4 {
+ return Half4(get(index1), get(index2), get(index3), get(index4))
+ }
+
+ operator fun get(index: Int) = when (index) {
+ 0 -> x
+ 1 -> y
+ 2 -> z
+ 3 -> w
+ else -> throw IllegalArgumentException("index must be in 0..3")
+ }
+
+ operator fun get(index1: Int, index2: Int) = Half2(get(index1), get(index2))
+ operator fun get(index1: Int, index2: Int, index3: Int): Half3 {
+ return Half3(get(index1), get(index2), get(index3))
+ }
+ operator fun get(index1: Int, index2: Int, index3: Int, index4: Int): Half4 {
+ return Half4(get(index1), get(index2), get(index3), get(index4))
+ }
+
+ inline operator fun invoke(index: Int) = get(index - 1)
+
+ operator fun set(index: Int, v: Half) = when (index) {
+ 0 -> x = v
+ 1 -> y = v
+ 2 -> z = v
+ 3 -> w = v
+ else -> throw IllegalArgumentException("index must be in 0..3")
+ }
+
+ operator fun set(index1: Int, index2: Int, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ }
+
+ operator fun set(index1: Int, index2: Int, index3: Int, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ set(index3, v)
+ }
+
+ operator fun set(index1: Int, index2: Int, index3: Int, index4: Int, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ set(index3, v)
+ set(index4, v)
+ }
+
+ operator fun set(index: VectorComponent, v: Half) = when (index) {
+ VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v
+ VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v
+ VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v
+ VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w = v
+ }
+
+ operator fun set(index1: VectorComponent, index2: VectorComponent, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ }
+
+ operator fun set(
+ index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ set(index3, v)
+ }
+
+ operator fun set(
+ index1: VectorComponent, index2: VectorComponent,
+ index3: VectorComponent, index4: VectorComponent, v: Half) {
+ set(index1, v)
+ set(index2, v)
+ set(index3, v)
+ set(index4, v)
+ }
+
+ operator fun unaryMinus() = Half4(-x, -y, -z, -w)
+ operator fun inc() = Half4(x++, y++, z++, w++)
+ operator fun dec() = Half4(x--, y--, z--, w--)
+
+ inline operator fun plus(v: Half) = Half4(x + v, y + v, z + v, w + v)
+ inline operator fun minus(v: Half) = Half4(x - v, y - v, z - v, w - v)
+ inline operator fun times(v: Half) = Half4(x * v, y * v, z * v, w * v)
+ inline operator fun div(v: Half) = Half4(x / v, y / v, z / v, w / v)
+
+ inline operator fun plus(v: Half2) = Half4(x + v.x, y + v.y, z, w)
+ inline operator fun minus(v: Half2) = Half4(x - v.x, y - v.y, z, w)
+ inline operator fun times(v: Half2) = Half4(x * v.x, y * v.y, z, w)
+ inline operator fun div(v: Half2) = Half4(x / v.x, y / v.y, z, w)
+
+ inline operator fun plus(v: Half3) = Half4(x + v.x, y + v.y, z + v.z, w)
+ inline operator fun minus(v: Half3) = Half4(x - v.x, y - v.y, z - v.z, w)
+ inline operator fun times(v: Half3) = Half4(x * v.x, y * v.y, z * v.z, w)
+ inline operator fun div(v: Half3) = Half4(x / v.x, y / v.y, z / v.z, w)
+
+ inline operator fun plus(v: Half4) = Half4(x + v.x, y + v.y, z + v.z, w + v.w)
+ inline operator fun minus(v: Half4) = Half4(x - v.x, y - v.y, z - v.z, w - v.w)
+ inline operator fun times(v: Half4) = Half4(x * v.x, y * v.y, z * v.z, w * v.w)
+ inline operator fun div(v: Half4) = Half4(x / v.x, y / v.y, z / v.z, w / v.w)
+
+ inline fun transform(block: (Half) -> Half): Half4 {
+ x = block(x)
+ y = block(y)
+ z = block(z)
+ w = block(w)
+ return this
+ }
+
+ fun toFloatArray() = floatArrayOf(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat())
+}
+
+inline fun min(v: Half2) = min(v.x, v.y)
+inline fun min(a: Half2, b: Half2) = Half2(min(a.x, b.x), min(a.y, b.y))
+inline fun max(v: Half2) = max(v.x, v.y)
+inline fun max(a: Half2, b: Half2) = Half2(max(a.x, b.x), max(a.y, b.y))
+
+inline fun transform(v: Half2, block: (Half) -> Half) = v.copy().transform(block)
+
+inline fun lessThan(a: Half2, b: Half) = Bool2(a.x < b, a.y < b)
+inline fun lessThan(a: Half2, b: Half2) = Bool2(a.x < b.x, a.y < b.y)
+inline fun lessThanEqual(a: Half2, b: Half) = Bool2(a.x <= b, a.y <= b)
+inline fun lessThanEqual(a: Half2, b: Half2) = Bool2(a.x <= b.x, a.y <= b.y)
+inline fun greaterThan(a: Half2, b: Half) = Bool2(a.x > b, a.y > b)
+inline fun greaterThan(a: Half2, b: Half2) = Bool2(a.x > b.y, a.y > b.y)
+inline fun greaterThanEqual(a: Half2, b: Half) = Bool2(a.x >= b, a.y >= b)
+inline fun greaterThanEqual(a: Half2, b: Half2) = Bool2(a.x >= b.x, a.y >= b.y)
+inline fun equal(a: Half2, b: Half) = Bool2(a.x == b, a.y == b)
+inline fun equal(a: Half2, b: Half2) = Bool2(a.x == b.x, a.y == b.y)
+inline fun notEqual(a: Half2, b: Half) = Bool2(a.x != b, a.y != b)
+inline fun notEqual(a: Half2, b: Half2) = Bool2(a.x != b.x, a.y != b.y)
+
+inline infix fun Half2.lt(b: Half) = Bool2(x < b, y < b)
+inline infix fun Half2.lt(b: Half2) = Bool2(x < b.x, y < b.y)
+inline infix fun Half2.lte(b: Half) = Bool2(x <= b, y <= b)
+inline infix fun Half2.lte(b: Half2) = Bool2(x <= b.x, y <= b.y)
+inline infix fun Half2.gt(b: Half) = Bool2(x > b, y > b)
+inline infix fun Half2.gt(b: Half2) = Bool2(x > b.x, y > b.y)
+inline infix fun Half2.gte(b: Half) = Bool2(x >= b, y >= b)
+inline infix fun Half2.gte(b: Half2) = Bool2(x >= b.x, y >= b.y)
+inline infix fun Half2.eq(b: Half) = Bool2(x == b, y == b)
+inline infix fun Half2.eq(b: Half2) = Bool2(x == b.x, y == b.y)
+inline infix fun Half2.neq(b: Half) = Bool2(x != b, y != b)
+inline infix fun Half2.neq(b: Half2) = Bool2(x != b.x, y != b.y)
+
+inline operator fun Half.plus(v: Half3) = Half3(this + v.x, this + v.y, this + v.z)
+inline operator fun Half.minus(v: Half3) = Half3(this - v.x, this - v.y, this - v.z)
+inline operator fun Half.times(v: Half3) = Half3(this * v.x, this * v.y, this * v.z)
+inline operator fun Half.div(v: Half3) = Half3(this / v.x, this / v.y, this / v.z)
+
+inline fun abs(v: Half3) = Half3(abs(v.x), abs(v.y), abs(v.z))
+inline fun length(v: Half3) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
+inline fun length2(v: Half3) = v.x * v.x + v.y * v.y + v.z * v.z
+inline fun distance(a: Half3, b: Half3) = length(a - b)
+inline fun dot(a: Half3, b: Half3) = a.x * b.x + a.y * b.y + a.z * b.z
+inline fun cross(a: Half3, b: Half3): Half3 {
+ return Half3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x)
+}
+inline infix fun Half3.x(v: Half3): Half3 {
+ return Half3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x)
+}
+fun normalize(v: Half3): Half3 {
+ val l = HALF_ONE / length(v)
+ return Half3(v.x * l, v.y * l, v.z * l)
+}
+
+inline fun reflect(i: Half3, n: Half3) = i - HALF_TWO * dot(n, i) * n
+fun refract(i: Half3, n: Half3, eta: Half): Half3 {
+ val d = dot(n, i)
+ val k = HALF_ONE - eta * eta * (HALF_ONE - sqr(d))
+ return if (k < Half.POSITIVE_ZERO) Half3() else eta * i - (eta * d + sqrt(k)) * n
+}
+
+inline fun clamp(v: Half3, min: Half, max: Half): Half3 {
+ return Half3(
+ clamp(v.x, min, max),
+ clamp(v.y, min, max),
+ clamp(v.z, min, max)
+ )
+}
+
+inline fun clamp(v: Half3, min: Half3, max: Half3): Half3 {
+ return Half3(
+ clamp(v.x, min.x, max.x),
+ clamp(v.y, min.y, max.y),
+ clamp(v.z, min.z, max.z)
+ )
+}
+
+inline fun mix(a: Half3, b: Half3, x: Half): Half3 {
+ return Half3(
+ mix(a.x, b.x, x),
+ mix(a.y, b.y, x),
+ mix(a.z, b.z, x)
+ )
+}
+
+inline fun mix(a: Half3, b: Half3, x: Half3): Half3 {
+ return Half3(
+ mix(a.x, b.x, x.x),
+ mix(a.y, b.y, x.y),
+ mix(a.z, b.z, x.z)
+ )
+}
+
+inline fun min(v: Half3) = min(v.x, min(v.y, v.z))
+inline fun min(a: Half3, b: Half3) = Half3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z))
+inline fun max(v: Half3) = max(v.x, max(v.y, v.z))
+inline fun max(a: Half3, b: Half3) = Half3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z))
+
+inline fun transform(v: Half3, block: (Half) -> Half) = v.copy().transform(block)
+
+inline fun lessThan(a: Half3, b: Half) = Bool3(a.x < b, a.y < b, a.z < b)
+inline fun lessThan(a: Half3, b: Half3) = Bool3(a.x < b.x, a.y < b.y, a.z < b.z)
+inline fun lessThanEqual(a: Half3, b: Half) = Bool3(a.x <= b, a.y <= b, a.z <= b)
+inline fun lessThanEqual(a: Half3, b: Half3) = Bool3(a.x <= b.x, a.y <= b.y, a.z <= b.z)
+inline fun greaterThan(a: Half3, b: Half) = Bool3(a.x > b, a.y > b, a.z > b)
+inline fun greaterThan(a: Half3, b: Half3) = Bool3(a.x > b.y, a.y > b.y, a.z > b.z)
+inline fun greaterThanEqual(a: Half3, b: Half) = Bool3(a.x >= b, a.y >= b, a.z >= b)
+inline fun greaterThanEqual(a: Half3, b: Half3) = Bool3(a.x >= b.x, a.y >= b.y, a.z >= b.z)
+inline fun equal(a: Half3, b: Half) = Bool3(a.x == b, a.y == b, a.z == b)
+inline fun equal(a: Half3, b: Half3) = Bool3(a.x == b.x, a.y == b.y, a.z == b.z)
+inline fun notEqual(a: Half3, b: Half) = Bool3(a.x != b, a.y != b, a.z != b)
+inline fun notEqual(a: Half3, b: Half3) = Bool3(a.x != b.x, a.y != b.y, a.z != b.z)
+
+inline infix fun Half3.lt(b: Half) = Bool3(x < b, y < b, z < b)
+inline infix fun Half3.lt(b: Half3) = Bool3(x < b.x, y < b.y, z < b.z)
+inline infix fun Half3.lte(b: Half) = Bool3(x <= b, y <= b, z <= b)
+inline infix fun Half3.lte(b: Half3) = Bool3(x <= b.x, y <= b.y, z <= b.z)
+inline infix fun Half3.gt(b: Half) = Bool3(x > b, y > b, z > b)
+inline infix fun Half3.gt(b: Half3) = Bool3(x > b.x, y > b.y, z > b.z)
+inline infix fun Half3.gte(b: Half) = Bool3(x >= b, y >= b, z >= b)
+inline infix fun Half3.gte(b: Half3) = Bool3(x >= b.x, y >= b.y, z >= b.z)
+inline infix fun Half3.eq(b: Half) = Bool3(x == b, y == b, z == b)
+inline infix fun Half3.eq(b: Half3) = Bool3(x == b.x, y == b.y, z == b.z)
+inline infix fun Half3.neq(b: Half) = Bool3(x != b, y != b, z != b)
+inline infix fun Half3.neq(b: Half3) = Bool3(x != b.x, y != b.y, z != b.z)
+
+inline operator fun Half.plus(v: Half4) = Half4(this + v.x, this + v.y, this + v.z, this + v.w)
+inline operator fun Half.minus(v: Half4) = Half4(this - v.x, this - v.y, this - v.z, this - v.w)
+inline operator fun Half.times(v: Half4) = Half4(this * v.x, this * v.y, this * v.z, this * v.w)
+inline operator fun Half.div(v: Half4) = Half4(this / v.x, this / v.y, this / v.z, this / v.w)
+
+inline fun abs(v: Half4) = Half4(abs(v.x), abs(v.y), abs(v.z), abs(v.w))
+inline fun length(v: Half4) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w)
+inline fun length2(v: Half4) = v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w
+inline fun distance(a: Half4, b: Half4) = length(a - b)
+inline fun dot(a: Half4, b: Half4) = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
+fun normalize(v: Half4): Half4 {
+ val l = HALF_ONE / length(v)
+ return Half4(v.x * l, v.y * l, v.z * l, v.w * l)
+}
+
+inline fun clamp(v: Half4, min: Half, max: Half): Half4 {
+ return Half4(
+ clamp(v.x, min, max),
+ clamp(v.y, min, max),
+ clamp(v.z, min, max),
+ clamp(v.w, min, max)
+ )
+}
+
+inline fun clamp(v: Half4, min: Half4, max: Half4): Half4 {
+ return Half4(
+ clamp(v.x, min.x, max.x),
+ clamp(v.y, min.y, max.y),
+ clamp(v.z, min.z, max.z),
+ clamp(v.w, min.z, max.w)
+ )
+}
+
+inline fun mix(a: Half4, b: Half4, x: Half): Half4 {
+ return Half4(
+ mix(a.x, b.x, x),
+ mix(a.y, b.y, x),
+ mix(a.z, b.z, x),
+ mix(a.w, b.w, x)
+ )
+}
+
+inline fun mix(a: Half4, b: Half4, x: Half4): Half4 {
+ return Half4(
+ mix(a.x, b.x, x.x),
+ mix(a.y, b.y, x.y),
+ mix(a.z, b.z, x.z),
+ mix(a.w, b.w, x.w))
+}
+
+inline fun min(v: Half4) = min(v.x, min(v.y, min(v.z, v.w)))
+inline fun min(a: Half4, b: Half4): Half4 {
+ return Half4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w))
+}
+inline fun max(v: Half4) = max(v.x, max(v.y, max(v.z, v.w)))
+inline fun max(a: Half4, b: Half4): Half4 {
+ return Half4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w))
+}
+
+inline fun transform(v: Half4, block: (Half) -> Half) = v.copy().transform(block)
+
+inline fun lessThan(a: Half4, b: Half) = Bool4(a.x < b, a.y < b, a.z < b, a.w < b)
+inline fun lessThan(a: Half4, b: Half4) = Bool4(a.x < b.x, a.y < b.y, a.z < b.z, a.w < b.w)
+inline fun lessThanEqual(a: Half4, b: Half) = Bool4(a.x <= b, a.y <= b, a.z <= b, a.w <= b)
+inline fun lessThanEqual(a: Half4, b: Half4) = Bool4(a.x <= b.x, a.y <= b.y, a.z <= b.z, a.w <= b.w)
+inline fun greaterThan(a: Half4, b: Half) = Bool4(a.x > b, a.y > b, a.z > b, a.w > b)
+inline fun greaterThan(a: Half4, b: Half4) = Bool4(a.x > b.y, a.y > b.y, a.z > b.z, a.w > b.w)
+inline fun greaterThanEqual(a: Half4, b: Half) = Bool4(a.x >= b, a.y >= b, a.z >= b, a.w >= b)
+inline fun greaterThanEqual(a: Half4, b: Half4) = Bool4(a.x >= b.x, a.y >= b.y, a.z >= b.z, a.w >= b.w)
+inline fun equal(a: Half4, b: Half) = Bool4(a.x == b, a.y == b, a.z == b, a.w == b)
+inline fun equal(a: Half4, b: Half4) = Bool4(a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w)
+inline fun notEqual(a: Half4, b: Half) = Bool4(a.x != b, a.y != b, a.z != b, a.w != b)
+inline fun notEqual(a: Half4, b: Half4) = Bool4(a.x != b.x, a.y != b.y, a.z != b.z, a.w != b.w)
+
+inline infix fun Half4.lt(b: Half) = Bool4(x < b, y < b, z < b, a < b)
+inline infix fun Half4.lt(b: Half4) = Bool4(x < b.x, y < b.y, z < b.z, w < b.w)
+inline infix fun Half4.lte(b: Half) = Bool4(x <= b, y <= b, z <= b, w <= b)
+inline infix fun Half4.lte(b: Half4) = Bool4(x <= b.x, y <= b.y, z <= b.z, w <= b.w)
+inline infix fun Half4.gt(b: Half) = Bool4(x > b, y > b, z > b, w > b)
+inline infix fun Half4.gt(b: Half4) = Bool4(x > b.x, y > b.y, z > b.z, w > b.w)
+inline infix fun Half4.gte(b: Half) = Bool4(x >= b, y >= b, z >= b, w >= b)
+inline infix fun Half4.gte(b: Half4) = Bool4(x >= b.x, y >= b.y, z >= b.z, w >= b.w)
+inline infix fun Half4.eq(b: Half) = Bool4(x == b, y == b, z == b, w == b)
+inline infix fun Half4.eq(b: Half4) = Bool4(x == b.x, y == b.y, z == b.z, w == b.w)
+inline infix fun Half4.neq(b: Half) = Bool4(x != b, y != b, z != b, w != b)
+inline infix fun Half4.neq(b: Half4) = Bool4(x != b.x, y != b.y, z != b.z, w != b.w)
diff --git a/android/gltfio-android/CMakeLists.txt b/android/gltfio-android/CMakeLists.txt
index a3bf446307d..72acf110e25 100644
--- a/android/gltfio-android/CMakeLists.txt
+++ b/android/gltfio-android/CMakeLists.txt
@@ -44,6 +44,7 @@ set_target_properties(uberarchive PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberarchive.a)
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map")
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
set(GLTFIO_SRCS
${GLTFIO_DIR}/include/gltfio/Animator.h
@@ -52,6 +53,7 @@ set(GLTFIO_SRCS
${GLTFIO_DIR}/include/gltfio/FilamentInstance.h
${GLTFIO_DIR}/include/gltfio/MaterialProvider.h
${GLTFIO_DIR}/include/gltfio/NodeManager.h
+ ${GLTFIO_DIR}/include/gltfio/TrsTransformManager.h
${GLTFIO_DIR}/include/gltfio/ResourceLoader.h
${GLTFIO_DIR}/include/gltfio/TextureProvider.h
${GLTFIO_DIR}/include/gltfio/math.h
@@ -69,18 +71,31 @@ set(GLTFIO_SRCS
${GLTFIO_DIR}/src/FilamentAsset.cpp
${GLTFIO_DIR}/src/FilamentInstance.cpp
${GLTFIO_DIR}/src/FNodeManager.h
+ ${GLTFIO_DIR}/src/FTrsTransformManager.h
${GLTFIO_DIR}/src/GltfEnums.h
${GLTFIO_DIR}/src/Ktx2Provider.cpp
${GLTFIO_DIR}/src/MaterialProvider.cpp
${GLTFIO_DIR}/src/NodeManager.cpp
+ ${GLTFIO_DIR}/src/TrsTransformManager.cpp
${GLTFIO_DIR}/src/ResourceLoader.cpp
${GLTFIO_DIR}/src/StbProvider.cpp
${GLTFIO_DIR}/src/TangentsJob.cpp
${GLTFIO_DIR}/src/TangentsJob.h
${GLTFIO_DIR}/src/UbershaderProvider.cpp
+ ${GLTFIO_DIR}/src/Utility.cpp
+ ${GLTFIO_DIR}/src/Utility.h
${GLTFIO_DIR}/src/Wireframe.cpp
${GLTFIO_DIR}/src/Wireframe.h
${GLTFIO_DIR}/src/downcast.h
+ ${GLTFIO_DIR}/src/extended/AssetLoaderExtended.cpp
+ ${GLTFIO_DIR}/src/extended/AssetLoaderExtended.h
+ ${GLTFIO_DIR}/src/extended/ResourceLoaderExtended.cpp
+ ${GLTFIO_DIR}/src/extended/ResourceLoaderExtended.h
+ ${GLTFIO_DIR}/src/extended/TangentsJobExtended.cpp
+ ${GLTFIO_DIR}/src/extended/TangentsJobExtended.h
+ ${GLTFIO_DIR}/src/extended/TangentSpaceMeshWrapper.cpp
+ ${GLTFIO_DIR}/src/extended/TangentSpaceMeshWrapper.h
+
src/main/cpp/Animator.cpp
src/main/cpp/AssetLoader.cpp
@@ -105,7 +120,6 @@ set(GLTFIO_INCLUDE_DIRS
../../third_party/cgltf
../../third_party/meshoptimizer/src
../../third_party/robin-map
- ../../third_party/hat-trie
../../third_party/stb
../../libs/utils/include
../../libs/ktxreader/include
diff --git a/android/gltfio-android/build.gradle b/android/gltfio-android/build.gradle
index a4a3113fc78..21703aee1db 100644
--- a/android/gltfio-android/build.gradle
+++ b/android/gltfio-android/build.gradle
@@ -1,12 +1,6 @@
android {
namespace 'com.google.android.filament.gltfio'
- flavorDimensions "functionality"
- productFlavors {
- full {
- dimension "functionality"
- }
- }
packagingOptions {
// No need to package up the following shared libs, which arise as a side effect of our
// externalNativeBuild dependencies. When clients pick and choose from project-level gradle
@@ -16,6 +10,13 @@ android {
excludes += ['lib/*/libfilament-jni.so']
}
}
+
+ publishing {
+ singleVariant("release") {
+ withSourcesJar()
+ withJavadocJar()
+ }
+ }
}
dependencies {
@@ -29,9 +30,9 @@ apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
afterEvaluate { project ->
publishing {
publications {
- fullRelease(MavenPublication) {
+ release(MavenPublication) {
artifactId = POM_ARTIFACT_ID_FULL
- from components.fullRelease
+ from components.release
}
}
}
diff --git a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentInstance.java b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentInstance.java
index 71d286cda88..9998ee0cacb 100644
--- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentInstance.java
+++ b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentInstance.java
@@ -144,7 +144,7 @@ public int getJointCountAt(@IntRange(from = 0) int skinIndex) {
*
* Ignored if variantIndex is out of bounds.
*/
- void applyMaterialVariant(@IntRange(from = 0) int variantIndex) {
+ public void applyMaterialVariant(@IntRange(from = 0) int variantIndex) {
nApplyMaterialVariant(mNativeObject, variantIndex);
}
diff --git a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/MaterialProvider.java b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/MaterialProvider.java
index ee8aaa0fd11..83ae3ab110c 100644
--- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/MaterialProvider.java
+++ b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/MaterialProvider.java
@@ -28,47 +28,81 @@
@UsedByNative("AssetLoader.cpp")
public interface MaterialProvider {
-
/**
* MaterialKey specifies the requirements for a requested glTF material.
* The provider creates Filament materials that fulfill these requirements.
*/
@UsedByNative("MaterialKey.cpp")
public static class MaterialKey {
+ @UsedByNative("MaterialKey.cpp")
public boolean doubleSided;
+ @UsedByNative("MaterialKey.cpp")
public boolean unlit;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasVertexColors;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasBaseColorTexture;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasNormalTexture;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasOcclusionTexture;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasEmissiveTexture;
+ @UsedByNative("MaterialKey.cpp")
public boolean useSpecularGlossiness;
+ @UsedByNative("MaterialKey.cpp")
public int alphaMode; // 0 = OPAQUE, 1 = MASK, 2 = BLEND
+ @UsedByNative("MaterialKey.cpp")
public boolean enableDiagnostics;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasMetallicRoughnessTexture; // piggybacks with specularRoughness
+ @UsedByNative("MaterialKey.cpp")
public int metallicRoughnessUV; // piggybacks with specularRoughness
+ @UsedByNative("MaterialKey.cpp")
public int baseColorUV;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasClearCoatTexture;
+ @UsedByNative("MaterialKey.cpp")
public int clearCoatUV;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasClearCoatRoughnessTexture;
+ @UsedByNative("MaterialKey.cpp")
public int clearCoatRoughnessUV;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasClearCoatNormalTexture;
+ @UsedByNative("MaterialKey.cpp")
public int clearCoatNormalUV;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasClearCoat;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasTransmission;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasTextureTransforms;
+ @UsedByNative("MaterialKey.cpp")
public int emissiveUV;
+ @UsedByNative("MaterialKey.cpp")
public int aoUV;
+ @UsedByNative("MaterialKey.cpp")
public int normalUV;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasTransmissionTexture;
+ @UsedByNative("MaterialKey.cpp")
public int transmissionUV;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasSheenColorTexture;
+ @UsedByNative("MaterialKey.cpp")
public int sheenColorUV;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasSheenRoughnessTexture;
+ @UsedByNative("MaterialKey.cpp")
public int sheenRoughnessUV;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasVolumeThicknessTexture;
+ @UsedByNative("MaterialKey.cpp")
public int volumeThicknessUV;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasSheen;
+ @UsedByNative("MaterialKey.cpp")
public boolean hasIOR;
public MaterialKey() {}
diff --git a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/UbershaderProvider.java b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/UbershaderProvider.java
index f9260599526..bf01da6c166 100644
--- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/UbershaderProvider.java
+++ b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/UbershaderProvider.java
@@ -20,6 +20,7 @@
import com.google.android.filament.MaterialInstance;
import com.google.android.filament.Material;
import com.google.android.filament.VertexBuffer;
+import com.google.android.filament.proguard.UsedByNative;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -94,6 +95,7 @@ public void destroyMaterials() {
nDestroyMaterials(mNativeObject);
}
+ @UsedByNative("AssetLoader.cpp")
public long getNativeObject() {
return mNativeObject;
}
diff --git a/android/gradle.properties b/android/gradle.properties
index e9b5cbe0fe4..e4f778ef79e 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
-VERSION_NAME=1.31.1
+VERSION_NAME=1.56.6
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -18,7 +18,12 @@ POM_DEVELOPER_NAME=Filament Team
org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=false
+android.nonFinalResIds=false
com.google.android.filament.tools-dir=../../../out/release/filament
com.google.android.filament.dist-dir=../out/android-release/filament
com.google.android.filament.abis=all
+#com.google.android.filament.matdbg
+#com.google.android.filament.matnopt
diff --git a/android/gradle/gradle-mvn-push.gradle b/android/gradle/gradle-mvn-push.gradle
index 18ee2a3eb36..c6bf2751f6d 100644
--- a/android/gradle/gradle-mvn-push.gradle
+++ b/android/gradle/gradle-mvn-push.gradle
@@ -86,63 +86,10 @@ afterEvaluate { project ->
}
}
- if (project.getPlugins().hasPlugin('com.android.application') ||
- project.getPlugins().hasPlugin('com.android.library')) {
-
- task androidJavadocs(type: Javadoc) {
- source = android.sourceSets.main.java.source
- classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
- excludes = ['**/*.kt']
- }
-
- task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
- classifier = 'javadoc'
- from androidJavadocs.destinationDir
- }
-
- task androidSourcesJar(type: Jar) {
- classifier = 'sources'
- from android.sourceSets.main.java.source
- }
- }
-
- if (JavaVersion.current().isJava8Compatible()) {
- allprojects {
- tasks.withType(Javadoc) {
- options.addStringOption('Xdoclint:none', '-quiet')
- }
- }
- }
-
- if (JavaVersion.current().isJava9Compatible()) {
- allprojects {
- tasks.withType(Javadoc) {
- options.addBooleanOption('html5', true)
- }
- }
- }
-
- artifacts {
- if (project.getPlugins().hasPlugin('com.android.application') ||
- project.getPlugins().hasPlugin('com.android.library')) {
- archives androidSourcesJar
- archives androidJavadocsJar
- }
- }
-
- android.libraryVariants.all { variant ->
- tasks.androidJavadocs.doFirst {
- classpath += files(variant.javaCompileProvider.get().classpath.files.join(File.pathSeparator))
- }
- }
-
publishing.publications.all { publication ->
publication.groupId = GROUP
publication.version = VERSION_NAME
- publication.artifact androidSourcesJar
- publication.artifact androidJavadocsJar
-
configurePom(publication.pom)
}
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 37da3ec8454..e6abf1cccf0 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Wed Nov 17 10:40:18 PST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/android/samples/README.md b/android/samples/README.md
index 151583bb82d..cdfc93e2e21 100644
--- a/android/samples/README.md
+++ b/android/samples/README.md
@@ -87,9 +87,9 @@ compile Filament's native library and Filament's AAR for this project. The easie
is to install all the required dependencies and to run the following commands at the root of the
source tree:
-```
-$ ./build.sh -p desktop -i release
-$ ./build.sh -p android release
+```shell
+./build.sh -p desktop -i release
+./build.sh -p android release
```
This will build all the native components and the AAR required by this sample application.
@@ -100,8 +100,8 @@ distribution/install directory for desktop (produced by make/ninja install). Thi
contain `bin/matc` and `bin/cmgen`.
Example:
-```
-$ ./gradlew -Pfilament_tools_dir=../../dist-release assembleDebug
+```shell
+./gradlew -Pfilament_tools_dir=../../dist-release assembleDebug
```
## Important: SDK location
@@ -110,14 +110,24 @@ Either ensure your `ANDROID_HOME` environment variable is set or make sure the r
contains a `local.properties` file with the `sdk.dir` property pointing to your installation of
the Android SDK.
-## Android Studio
+## Compiling
+
+### Android Studio
You must use the latest stable release of Android Studio. To open the project, point Studio to the
`android` folder. After opening the project and syncing to gradle, select the sample of your choice
using the drop-down widget in the toolbar.
-## Compiling
-
To compile and run each sample make sure you have selected the appropriate build variant
(arm7, arm8, x86 or x86_64). If you are not sure you can simply select the "universal"
variant which includes all the other ones.
+
+### Command Line
+
+From the `android` directory in the project root:
+
+```shell
+./gradlew :samples:sample-hello-triangle:installDebug
+```
+
+Replace `sample-hello-triangle` with your preferred project.
diff --git a/android/samples/sample-gltf-viewer/build.gradle b/android/samples/sample-gltf-viewer/build.gradle
index 5f1b6de3088..e2e83b94717 100644
--- a/android/samples/sample-gltf-viewer/build.gradle
+++ b/android/samples/sample-gltf-viewer/build.gradle
@@ -6,6 +6,10 @@ plugins {
project.ext.isSample = true
+kotlin {
+ jvmToolchain(versions.jdk)
+}
+
filamentTools {
cmgenArgs = "-q --format=ktx --size=256 --extract-blur=0.1 --deploy=src/main/assets/envs/default_env"
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/lightroom_14b.hdr")
@@ -13,7 +17,7 @@ filamentTools {
}
// don't forget to update MainACtivity.kt when/if changing this.
-task copyMesh(type: Copy) {
+tasks.register('copyMesh', Copy) {
from "../../../third_party/models/BusterDrone"
into "src/main/assets/models"
}
@@ -30,9 +34,8 @@ android {
compileSdkVersion versions.compileSdk
defaultConfig {
applicationId "com.google.android.filament.gltf"
- minSdkVersion 19
+ minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
- missingDimensionStrategy 'functionality', 'full'
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
@@ -40,6 +43,11 @@ android {
dependenciesInfo {
includeInApk = false
}
+
+ compileOptions {
+ sourceCompatibility versions.jdk
+ targetCompatibility versions.jdk
+ }
}
dependencies {
diff --git a/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt b/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt
index 6e449261fdd..102c4944e6e 100644
--- a/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt
+++ b/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt
@@ -26,8 +26,10 @@ import android.widget.TextView
import android.widget.Toast
import com.google.android.filament.Fence
import com.google.android.filament.IndirectLight
+import com.google.android.filament.Material
import com.google.android.filament.Skybox
import com.google.android.filament.View
+import com.google.android.filament.View.OnPickCallback
import com.google.android.filament.utils.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -56,7 +58,9 @@ class MainActivity : Activity() {
private lateinit var modelViewer: ModelViewer
private lateinit var titlebarHint: TextView
private val doubleTapListener = DoubleTapListener()
+ private val singleTapListener = SingleTapListener()
private lateinit var doubleTapDetector: GestureDetector
+ private lateinit var singleTapDetector: GestureDetector
private var remoteServer: RemoteServer? = null
private var statusToast: Toast? = null
private var statusText: String? = null
@@ -77,6 +81,7 @@ class MainActivity : Activity() {
choreographer = Choreographer.getInstance()
doubleTapDetector = GestureDetector(applicationContext, doubleTapListener)
+ singleTapDetector = GestureDetector(applicationContext, singleTapListener)
modelViewer = ModelViewer(surfaceView)
viewerContent.view = modelViewer.view
@@ -88,6 +93,7 @@ class MainActivity : Activity() {
surfaceView.setOnTouchListener { _, event ->
modelViewer.onTouchEvent(event)
doubleTapDetector.onTouchEvent(event)
+ singleTapDetector.onTouchEvent(event)
true
}
@@ -229,6 +235,7 @@ class MainActivity : Activity() {
modelViewer.scene.skybox = sky
modelViewer.scene.indirectLight = ibl
viewerContent.indirectLight = ibl
+
}
}
}
@@ -337,6 +344,11 @@ class MainActivity : Activity() {
remoteServer?.close()
}
+ override fun onBackPressed() {
+ super.onBackPressed()
+ finish()
+ }
+
fun loadModelData(message: RemoteServer.ReceivedMessage) {
Log.i(TAG, "Downloaded model ${message.label} (${message.buffer.capacity()} bytes)")
clearStatusText()
@@ -356,6 +368,8 @@ class MainActivity : Activity() {
automation.applySettings(modelViewer.engine, json, viewerContent)
modelViewer.view.colorGrading = automation.getColorGrading(modelViewer.engine)
modelViewer.cameraFocalLength = automation.viewerOptions.cameraFocalLength
+ modelViewer.cameraNear = automation.viewerOptions.cameraNear
+ modelViewer.cameraFar = automation.viewerOptions.cameraFar
updateRootTransform()
}
@@ -379,6 +393,36 @@ class MainActivity : Activity() {
Log.i(TAG, "The Filament backend took $total ms to load the model geometry.")
modelViewer.engine.destroyFence(it)
loadStartFence = null
+
+ val materials = mutableSetOf-For the specular term, \(f_m\) is a mirror BRDF that can be modeled with the Fresnel law, noted \(F\) in the Cook-Torrance approximation of the microfacet model integration: +For the specular term, \(f_r\) is a mirror BRDF that can be modeled with the Fresnel law, noted \(F\) in the Cook-Torrance approximation of the microfacet model integration:
@@ -857,7 +857,7 @@ // perceptually linear roughness to roughness (see parameterization) float roughness = perceptualRoughness * perceptualRoughness; - float D = D_GGX(NoH, a); + float D = D_GGX(NoH, roughness); vec3 F = F_Schlick(LoH, f0); float V = V_SmithGGXCorrelated(NoV, NoL, roughness); @@ -3164,7 +3164,7 @@
However, in practice we're using importance sampling which needs to take the \(pdf\) of the distribution -into account and adds a term \(\frac{\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>}\). +into account and adds a term \(\frac{4\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>}\). See Importance Sampling For The IBL section:
@@ -5274,7 +5274,7 @@
$$\begin{equation} -p(T_r(x)) = p(x) |J(T_r)| +p(T_r(x)) = p(x) |J(T_r)|^{-1} \end{equation}$$
@@ -5285,8 +5285,8 @@
$$\begin{equation}\label{iblPDF} -p(l,v,\Theta) = D(h,\alpha) \left< \NoH \right> |J_{h \rightarrow l}| \\ -|J_{h \rightarrow l}| = \frac{1}{4 \left< \VoH \right>} +p(l,v,\Theta) = D(h,\alpha) \left< \NoH \right> |J_{h \rightarrow l}|^{-1} \\ +|J_{h \rightarrow l}| = 4 \left< \VoH \right> \end{equation}$$
@@ -5776,7 +5776,7 @@ $$\begin{align*} C^0_l &= 2\pi \int_0^{\pi} \left< cos \theta \right> y^0_l(\theta) sin \theta d\theta \\ -C^0_l &= 2\pi K^)_l \int_0^{\frac{\pi}{2}} P^0_l(cos \theta) cos \theta sin \theta d\theta \\ +C^0_l &= 2\pi K^m_l \int_0^{\frac{\pi}{2}} P^0_l(cos \theta) cos \theta sin \theta d\theta \\ C^m_l &= 0, m != 0 \end{align*}$$ diff --git a/docs/Filament.md.html b/docs/Filament.md.html index 9a37a5c8321..167f967bd51 100644 --- a/docs/Filament.md.html +++ b/docs/Filament.md.html @@ -200,7 +200,7 @@ ## Specular BRDF -For the specular term, $f_m$ is a mirror BRDF that can be modeled with the Fresnel law, noted $F$ in the Cook-Torrance approximation of the microfacet model integration: +For the specular term, $f_r$ is a mirror BRDF that can be modeled with the Fresnel law, noted $F$ in the Cook-Torrance approximation of the microfacet model integration: $$\begin{equation} f_r(v,l) = \frac{D(h, \alpha) G(v, l, \alpha) F(v, h, f0)}{4(\NoV)(\NoL)} @@ -485,7 +485,7 @@ // perceptually linear roughness to roughness (see parameterization) float roughness = perceptualRoughness * perceptualRoughness; - float D = D_GGX(NoH, a); + float D = D_GGX(NoH, roughness); vec3 F = F_Schlick(LoH, f0); float V = V_SmithGGXCorrelated(NoV, NoL, roughness); @@ -2016,7 +2016,7 @@ \end{equation}$$ However, in practice we're using _importance sampling_ which needs to take the $pdf$ of the distribution -into account and adds a term $\frac{\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>}$. +into account and adds a term $\frac{4\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>}$. See Importance Sampling For The IBL section: $$\begin{equation}\label{iblImportanceSampling} @@ -3486,15 +3486,15 @@ **do not** have the same PDF as $h_i$. The PDF of a transformed distribution is given by: $$\begin{equation} -p(T_r(x)) = p(x) |J(T_r)| +p(T_r(x)) = p(x) |J(T_r)|^{-1} \end{equation}$$ Where $|J(T_r)|$ is the determinant of the Jacobian of the transform. In our case we're considering the transform from $h_i$ to $l_i$ and the determinant of its Jacobian is given in \ref{iblPDF}. $$\begin{equation}\label{iblPDF} -p(l,v,\Theta) = D(h,\alpha) \left< \NoH \right> |J_{h \rightarrow l}| \\ -|J_{h \rightarrow l}| = \frac{1}{4 \left< \VoH \right>} +p(l,v,\Theta) = D(h,\alpha) \left< \NoH \right> |J_{h \rightarrow l}|^{-1} \\ +|J_{h \rightarrow l}| = 4 \left< \VoH \right> \end{equation}$$ ### Choosing important directions diff --git a/docs/Materials.html b/docs/Materials.html index 6fcb8bdb506..339a3b0cae0 100644 --- a/docs/Materials.html +++ b/docs/Materials.html @@ -89,36 +89,39 @@ 4.2.2 General: featureLevel
@@ -778,7 +785,7 @@
The bentNormal
property defines the average unoccluded direction at a point on the surface. It is
used to improve the accuracy of indirect lighting. Bent normals can also improve the quality of
-specular ambient occlusion (see section 4.2.30 about
+specular ambient occlusion (see section 4.2.33 about
specularAmbientOcclusion
).
@@ -893,7 +900,7 @@
The appearance of a refractive material will greatly depend on the refractionType
and
-refractionMode
settings of the material. Refer to section 4.2.19 and section 4.2.18
+refractionMode
settings of the material. Refer to section 4.2.21 and section 4.2.20
for more information.
@@ -1564,13 +1571,69 @@ material.reflectance = materialParams.metallicReflectance.y; } } -
+ + +
array of constant objects + +
Each entry is an object with the properties name
and type
, both of string
type. The name
+ must be a valid GLSL identifier. Entries also have an optional default
, which can either be a
+ bool
or number
, depending on the type
of the constant. The type must be one of the types
+ described in table 15.
+
+
Type | Description | Default |
---|---|---|
int | A signed, 32 bit GLSL int | 0 |
float | A single-precision GLSL float | 0.0 |
bool | A GLSL bool | false |
+ +
Lists the constant parameters accepted by your material. These constants can be set, or
+ “specialized”, at runtime when loading a material package. Multiple materials can be loaded from
+ the same material package with differing constant parameter specializations. Once a material is
+ loaded from a material package, its constant parameters cannot be changed. Compared to regular
+ parameters, constant parameters allow the compiler to generate more efficient code. Access
+ constant parameters from the shader by prefixing the name with materialConstant_
. For example,
+ a constant parameter named myConstant
is accessed in the shader as
+ materialConstant_myConstant
. If a constant parameter is not set at runtime, the default is
+ used.
+
+
material {
+ constants : [
+ {
+ name : overrideAlpha,
+ type : bool
+ },
+ {
+ name : customAlpha,
+ type : float,
+ default : 0.5
+ }
+ ],
+ shadingModel : lit,
+ blending : transparent,
+}
+
+fragment {
+ void material(inout MaterialInputs material) {
+ prepareMaterial(material);
+ if (materialConstants_overrideAlpha) {
+ material.baseColor.a = materialConstants_customAlpha;
+ material.baseColor.rgb *= material.baseColor.a;
+ }
+ }
+}
+
array of string
-
Each entry must be any of dynamicLighting
, directionalLighting
, shadowReceiver
,skinning
or ssr
.
+
Each entry must be any of dynamicLighting
, directionalLighting
, shadowReceiver
,
+ skinning
, ssr
, or stereo
.
Used to specify a list of shader variants that the application guarantees will never be needed. These shader variants are skipped during the code generation phase, thus reducing @@ -1597,7 +1660,9 @@
vsm
, used when VSM shadows are enabled and the object is a shadow receiver
ssr
, used when screen-space reflections are enabled in the Viewssr
, used when screen-space reflections are enabled in the View
+stereo
, used when stereoscopic rendering is enabled in the Viewmaterial {
name : "Invisible shadow plane",
@@ -1606,7 +1671,7 @@
blending : transparent,
variantFilter : [ skinning ]
}
- @@ -1621,7 +1686,7 @@
material {
flipUV : false
}
- @@ -1637,7 +1702,7 @@
material {
quality : default
}
- @@ -1654,7 +1719,7 @@
material {
instanced : true
}
- @@ -1673,7 +1738,7 @@
material {
vertexDomainDeviceJittered : true
}
- @@ -1708,7 +1773,7 @@ material.baseColor.rgb *= getCustom0().rgb; } } -
@@ -1722,7 +1787,12 @@
declare a variable called eyeDirection
you can access it in the fragment shader using
variable_eyeDirection
. In the vertex shader, the interpolant name is simply a member of
the MaterialVertexInputs
structure (material.eyeDirection
in your example). Each
- interpolant is of type float4
(vec4
) in the shaders.
+ interpolant is of type float4
(vec4
) in the shaders. By default the precision of the
+ interpolant is highp
in both the vertex and fragment shaders.
+ An alternate syntax can be used to specify both the name and precision of the interpolant.
+ In this case the specified precision is used as-is in both fragment and vertex stages, in
+ particular if default
is specified the default precision is used is the fragment shader
+ (mediump
) and in the vertex shader (highp
).
material {
name : Skybox,
@@ -1733,7 +1803,11 @@
}
],
variables : [
- eyeDirection
+ eyeDirection,
+ {
+ name : eyeColor,
+ precision : medium
+ }
],
vertexDomain : device,
depthWrite : false,
@@ -1755,7 +1829,7 @@
material.eyeDirection.xyz = mulMat3x3Float3(getWorldFromViewMatrix(), u);
}
}
- @@ -1784,7 +1858,7 @@
material {
vertexDomain : device
}
- @@ -1800,13 +1874,13 @@
material {
interpolation : flat
}
-
string
-
Any of opaque
, transparent
, fade
, add
, masked
, multiply
, screen
. Defaults to opaque
.
+
Any of opaque
, transparent
, fade
, add
, masked
, multiply
, screen
, custom
. Defaults to opaque
.
Defines how/if the rendered object is blended with the content of the render target. The possible blending modes are: @@ -1836,12 +1910,44 @@
blendFunction
.material {
+When blending
is set to masked
, alpha to coverage is automatically enabled for the material.
+ If this behavior is undesirable, refer to the Rasterization: alphaToCoverage section to turn
+ alpha to coverage off using the alphaToCoverage
property.
+
+material {
blending : transparent
}
- Blending and transparency: postLightingBlending
+ Blending and transparency: blendFunction
+
+
+
+
- Type
object
+
+
- Fields
srcRGB
, srcA
, dstRGB
, dstA
+
+
- Description
- srcRGB: source function applied to the RGB channels
+ - srcA: source function applied to the alpha channel
+ - srcRGB: destination function applied to the RGB channels
+ - srcRGB: destination function applied to the alpha channel
+ The values possible for each functions are one of zero
, one
, srcColor
, oneMinusSrcColor
,
+ dstColor
, oneMinusDstColor
, srcAlpha
, oneMinusSrcAlpha
, dstAlpha
,
+ oneMinusDstAlpha
, srcAlphaSaturate
+
+
material {
+ blending : custom,
+ blendFunction :
+ {
+ srcRGB: one,
+ srcA: one,
+ dstRGB: oneMinusSrcColor,
+ dstA: oneMinusSrcAlpha
+ }
+ }
+ Blending and transparency: postLightingBlending
@@ -1871,7 +1977,7 @@
material {
postLightingBlending : add
}
- Blending and transparency: transparency
+ Blending and transparency: transparency
@@ -1918,7 +2024,7 @@
and sorting issues are minimized or eliminated
- Blending and transparency: maskThreshold
+ Blending and transparency: maskThreshold
@@ -1935,7 +2041,7 @@
blending : masked,
maskThreshold : 0.5
}
- @@ -1956,7 +2062,7 @@
material {
refractionMode : cubemap,
}
- @@ -1975,7 +2081,7 @@ refractionMode : cubemap, refractionType : thin, } -
@@ -1989,7 +2095,7 @@
material {
culling : none
}
- @@ -2002,7 +2108,7 @@
material {
colorWrite : false
}
- @@ -2015,7 +2121,7 @@
material {
depthWrite : false
}
- @@ -2029,7 +2135,7 @@
material {
depthCulling : false
}
- @@ -2054,7 +2160,34 @@ material.baseColor = materialParams.albedo; } } -
+ + +
boolean
+
+
true
or false
. Defaults to false
.
+
+
Enables or disables alpha to coverage. When alpha to coverage is enabled, the coverage of
+ fragment is derived from its alpha. This property is only meaningful when MSAA is enabled.
+ Note: setting blending
to masked
automatically enables alpha to coverage. If this is not
+ desired, you can override this behavior by setting alpha to coverage to false as in the
+ example below.
+
+
material {
+ name : "Alpha to coverage",
+ shadingModel : lit,
+ blending : masked,
+ alphaToCoverage : false
+}
+
+fragment {
+ void material(inout MaterialInputs material) {
+ prepareMaterial(material);
+ material.baseColor = materialParams.albedo;
+ }
+}
+ @@ -2071,7 +2204,7 @@ name : "Glossy metal", reflections : screenspace } -
@@ -2098,7 +2231,7 @@ material.baseColor = vec4(0.0, 0.0, 0.0, 0.7); } } -
@@ -2132,7 +2265,7 @@ by T-Art.
-@@ -2154,7 +2287,7 @@ (right).
-@@ -2183,7 +2316,7 @@ occclusion enabled and disabled.
-@@ -2209,7 +2342,7 @@ particularly visible under the hose.
-@@ -2226,7 +2359,7 @@
material {
specularAntiAliasing : true
}
- @@ -2241,7 +2374,7 @@
material {
specularAntiAliasingVariance : 0.2
}
- @@ -2255,7 +2388,7 @@
material {
specularAntiAliasingThreshold : 0.1
}
- @@ -2335,8 +2468,9 @@
To achieve good precision, the worldPosition
coordinate in the vertex shader is shifted by the
- camera position. To get the true world-space position, users can add this to
- getWorldOffset()
.
getUserWorldPosition()
, however be aware that the true world-position might not
+ be able to fit in a float
or might be represented with severely reduced precision.
@@ -2707,12 +2841,13 @@
Name | Type | Description |
---|---|---|
getResolution() | float4 | Dimensions of the view's effective viewport in pixels: width , height , 1 / width , 1 / height . This might be different from View::getViewport() for instance because of added rendering guard-bands. This can be used in conjunction with getNormalizedViewportCoord() to generate pixel coordinates. |
getWorldCameraPosition() | float3 | Position of the camera/eye in world space |
getWorldOffset() | float3 | The shift required to obtain API-level world space |
getResolution() | float4 | Dimensions of the view's effective (physical) viewport in pixels: width , height , 1 / width , 1 / height . This might be different from View::getViewport() for instance because of added rendering guard-bands. |
getWorldCameraPosition() | float3 | Position of the camera/eye in world space (see note below) |
getWorldOffset() | float3 | [deprecated] The shift required to obtain API-level world space. Use getUserWorldPosition() instead |
getUserWorldFromWorldMatrix() | float4×4 | Matrix that converts from world space to API-level (user) world space. |
getTime() | float | Current time as a remainder of 1 second. Yields a value between 0 and 1 |
getUserTime() | float4 | Current time in seconds: time , (double)time - time , 0 , 0 |
getUserTimeMode(float m) | float | Current time modulo m in seconds |
getUserTimeMod(float m) | float | Current time modulo m in seconds |
getExposure() | float | Photometric exposure of the camera |
getEV100() | float | Exposure value at ISO 100 of the camera |
getWorldOffset()
to getWorldCameraPosition()
.
+ materials can use getUserWorldFromWorldMatrix()
to transform getWorldCameraPosition()
.
+
+
+ + +
Name | Type | Description |
---|---|---|
getMaterialGlobal0() | float4 | A vec4 visible by all materials, its value is set by View::setMaterialGlobal(0, float4) . Its default value is {0,0,0,1}. |
getMaterialGlobal1() | float4 | A vec4 visible by all materials, its value is set by View::setMaterialGlobal(1, float4) . Its default value is {0,0,0,1}. |
getMaterialGlobal2() | float4 | A vec4 visible by all materials, its value is set by View::setMaterialGlobal(2, float4) . Its default value is {0,0,0,1}. |
getMaterialGlobal3() | float4 | A vec4 visible by all materials, its value is set by View::setMaterialGlobal(3, float4) . Its default value is {0,0,0,1}. |
@@ -2739,10 +2885,11 @@
@@ -2750,11 +2897,12 @@
Name | Type | Description |
---|---|---|
getWorldTangentFrame() | float3×3 | Matrix containing in each column the tangent (frame[0] ), bi-tangent (frame[1] ) and normal (frame[2] ) of the vertex in world space. If the material does not compute a tangent space normal for bump mapping or if the shading is not anisotropic, only the normal is valid in this matrix. |
getWorldPosition() | float3 | Position of the fragment in world space (see note below about world-space) |
getUserWorldPosition() | float3 | Position of the fragment in API-level (user) world-space (see note below about world-space) |
getWorldViewVector() | float3 | Normalized vector in world space from the fragment position to the eye |
getWorldNormalVector() | float3 | Normalized normal in world space, after bump mapping (must be used after prepareMaterial() ) |
getWorldGeometricNormalVector() | float3 | Normalized normal in world space, before bump mapping (can be used before prepareMaterial() ) |
getWorldReflectedVector() | float3 | Reflection of the view vector about the normal (must be used after prepareMaterial() ) |
getNormalizedViewportCoord() | float3 | Normalized viewport position (i.e. NDC coordinates normalized to [0, 1], can be used before prepareMaterial() ) |
getNormalizedViewportCoord() | float3 | Normalized user viewport position (i.e. NDC coordinates normalized to [0, 1] for the position, [1, 0] for the depth), can be used before prepareMaterial() ). Because the user viewport is smaller than the actual physical viewport, these coordinates can be negative or superior to 1 in the non-visible area of the physical viewport. |
getNdotV() | float | The result of dot(normal, view) , always strictly greater than 0 (must be used after prepareMaterial() ) |
getColor() | float4 | Interpolated color of the fragment, if the color attribute is required |
getUV0() | float2 | First interpolated set of UV coordinates, only available if the uv0 attribute is required |
Flag | Value | Usage |
---|---|---|
-o, —output | [path] | Specify the output file path |
-S, —optimize-size | N/A | Optimize compiled material for size instead of just performance |
-r, —reflect | parameters | Outputs the specified metadata as JSON |
-v, —variant-filter | [variant] | Filters out the specified, comma-separated variants |
@@ -2969,7 +3118,47 @@ Bitmap will be pre-multiplied by default. +
+
+
+
+The number of usable sampler parameters (e.g.: type is sampler2d
) in materials is limited and
+depends on the material properties, shading model, feature level and variant filter.
+
+
+
+
+unlit
materials can use up to 12 samplers by default.
+
+lit
materials can use up to 9 samplers by default, however if refractionMode
or reflectionMode
+is set to screenspace
that number is reduced to 8.
+
+
+
+Finally if variantFilter
contains the fog
filter, an extra sampler is made available, such that
+unlit
materials can use up to 13 and lit
materials up to 10 samplers by default.
+
+
+ + +16 samplers are available. + +
+ +
+
+ Be aware that external
samplers account for 2 regular samplers.
+