From 961860adeee38abbe415cd4eb9212cb8d244a893 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Tue, 7 Jan 2020 17:12:45 -0800 Subject: [PATCH] Introduce filament-utils and Java bindings for camutils. This library houses the KTX loader, camutils bindings, and a Kotlin math library. More will come later. Sample app is forthcoming. --- .github/workflows/android-continuous.yml | 4 + .github/workflows/release.yml | 1 + README.md | 3 +- android/filament-utils-android/.gitignore | 12 + android/filament-utils-android/CMakeLists.txt | 104 ++ android/filament-utils-android/build.gradle | 63 + .../filament-utils-android/gradle.properties | 3 + .../libfilament-utils-jni.map | 4 + .../libfilament-utils-jni.symbols | 1 + .../src/main/AndroidManifest.xml | 18 + .../src/main/cpp/Bookmark.cpp | 27 + .../src/main/cpp/Manipulator.cpp | 175 +++ .../src/main/cpp/Utils.cpp} | 23 +- .../android/filament/utils/Bookmark.java | 37 + .../android/filament/utils/GestureDetector.kt | 156 ++ .../android/filament/utils}/KtxLoader.java | 5 +- .../android/filament/utils/Manipulator.java | 369 +++++ .../google/android/filament/utils/Matrix.kt | 520 +++++++ .../com/google/android/filament/utils/Ray.kt | 21 + .../google/android/filament/utils/Scalar.kt | 43 + .../google/android/filament/utils/Utils.java | 27 + .../google/android/filament/utils/Vector.kt | 1299 +++++++++++++++++ android/gltfio-android/CMakeLists.txt | 6 - .../samples/sample-gltf-bloom/build.gradle | 1 + .../android/filament/bloom/MainActivity.kt | 3 +- android/settings.gradle | 1 + build.sh | 14 +- 27 files changed, 2923 insertions(+), 17 deletions(-) create mode 100644 android/filament-utils-android/.gitignore create mode 100644 android/filament-utils-android/CMakeLists.txt create mode 100644 android/filament-utils-android/build.gradle create mode 100644 android/filament-utils-android/gradle.properties create mode 100644 android/filament-utils-android/libfilament-utils-jni.map create mode 100644 android/filament-utils-android/libfilament-utils-jni.symbols create mode 100644 android/filament-utils-android/src/main/AndroidManifest.xml create mode 100644 android/filament-utils-android/src/main/cpp/Bookmark.cpp create mode 100644 android/filament-utils-android/src/main/cpp/Manipulator.cpp rename android/{gltfio-android/src/main/cpp/KtxLoader.cpp => filament-utils-android/src/main/cpp/Utils.cpp} (80%) create mode 100644 android/filament-utils-android/src/main/java/com/google/android/filament/utils/Bookmark.java create mode 100644 android/filament-utils-android/src/main/java/com/google/android/filament/utils/GestureDetector.kt rename android/{gltfio-android/src/main/java/com/google/android/filament/gltfio => filament-utils-android/src/main/java/com/google/android/filament/utils}/KtxLoader.java (97%) create mode 100644 android/filament-utils-android/src/main/java/com/google/android/filament/utils/Manipulator.java create mode 100644 android/filament-utils-android/src/main/java/com/google/android/filament/utils/Matrix.kt create mode 100644 android/filament-utils-android/src/main/java/com/google/android/filament/utils/Ray.kt create mode 100644 android/filament-utils-android/src/main/java/com/google/android/filament/utils/Scalar.kt create mode 100644 android/filament-utils-android/src/main/java/com/google/android/filament/utils/Utils.java create mode 100644 android/filament-utils-android/src/main/java/com/google/android/filament/utils/Vector.kt diff --git a/.github/workflows/android-continuous.yml b/.github/workflows/android-continuous.yml index 5bca0b16e0..f65cfbccdd 100644 --- a/.github/workflows/android-continuous.yml +++ b/.github/workflows/android-continuous.yml @@ -31,3 +31,7 @@ jobs: 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7e632450fe..95daea9fff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -107,6 +107,7 @@ jobs: mv out/filamat-android-full-release.aar out/filamat-${DATE}-full-android.aar mv out/filamat-android-lite-release.aar out/filamat-${DATE}-lite-android.aar mv out/gltfio-android-release.aar out/gltfio-${DATE}-android.aar + mv out/filament-utils-android-release.aar out/filament-utils-${DATE}-android.aar python3 build/common/upload-assets.py ${TAG} out/*.aar env: TAG: ${{ steps.git_ref.outputs.tag }} diff --git a/README.md b/README.md index ecf498c721..0000e7c424 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,11 @@ Here are all the libraries available in the group `com.google.android.filament`: - `filament-android`: the Filament rendering engine itself - `gltfio-android`: a glTF 2.0 loader for Filament, depends on `filament-android` +- `filament-utils-android`: KTX loading, Kotlin math, and camera utilities, depends on `gltfio-android` - `filamat-android-full`: a runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer - `filamat-android-lite`: a much smaller alternative to `filamat-android-full` that can only - generate OpenGL shaders. It does not provide validation nor optimizations + generate OpenGL shaders. It does not provide validation or optimizations ### Snapshots diff --git a/android/filament-utils-android/.gitignore b/android/filament-utils-android/.gitignore new file mode 100644 index 0000000000..6b7a98ade6 --- /dev/null +++ b/android/filament-utils-android/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +/.idea/caches +/.idea/gradle.xml +.DS_Store +/build +/captures +.externalNativeBuild +/.cxx \ No newline at end of file diff --git a/android/filament-utils-android/CMakeLists.txt b/android/filament-utils-android/CMakeLists.txt new file mode 100644 index 0000000000..d69acaba17 --- /dev/null +++ b/android/filament-utils-android/CMakeLists.txt @@ -0,0 +1,104 @@ +cmake_minimum_required(VERSION 3.6) + +set(FILAMENT_DIR ${FILAMENT_DIST_DIR}) + +set(DISABLE_GLTFIO_JNI TRUE) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../gltfio-android ${CMAKE_CURRENT_BINARY_DIR}/gltfio-android) + +add_library(filament STATIC IMPORTED) +set_target_properties(filament PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilament.a) + +add_library(backend STATIC IMPORTED) +set_target_properties(backend PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libbackend.a) + +add_library(camutils STATIC IMPORTED) +set_target_properties(camutils PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libcamutils.a) + +add_library(utils STATIC IMPORTED) +set_target_properties(utils PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a) + +add_library(filaflat STATIC IMPORTED) +set_target_properties(filaflat PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilaflat.a) + +add_library(image STATIC IMPORTED) +set_target_properties(image PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimage.a) + +add_library(ibl STATIC IMPORTED) +set_target_properties(ibl PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl.a) + +add_library(geometry STATIC IMPORTED) +set_target_properties(geometry PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libgeometry.a) + +add_library(filabridge STATIC IMPORTED) +set_target_properties(filabridge PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilabridge.a) + +add_library(gltfio STATIC IMPORTED) +set_target_properties(gltfio PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libgltfio_core.a) + +add_library(gltfio_resources STATIC IMPORTED) +set_target_properties(gltfio_resources PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libgltfio_resources.a) + +add_library(bluevk STATIC IMPORTED) +set_target_properties(bluevk PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libbluevk.a) + +add_library(smol-v STATIC IMPORTED) +set_target_properties(smol-v PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libsmol-v.a) + +include_directories(${FILAMENT_DIR}/include + .. + ../../libs/utils/include) + +set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.map") + +add_library(filament-utils-jni SHARED + src/main/cpp/Bookmark.cpp + src/main/cpp/Utils.cpp + src/main/cpp/Manipulator.cpp + + ../common/CallbackUtils.cpp + ../common/NioUtils.cpp + + $ + $ +) + +set_target_properties(filament-utils-jni PROPERTIES LINK_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.symbols) + +# The ordering in the following list is important because CMake does not have dependency information. +target_link_libraries(filament-utils-jni + gltfio + filament + backend + filaflat + filabridge + camutils + geometry + image + ibl + utils + log + GLESv3 + EGL + android + jnigraphics + gltfio_resources + m +) + +if (FILAMENT_SUPPORTS_VULKAN) + target_link_libraries(filament-utils-jni bluevk smol-v) +endif() diff --git a/android/filament-utils-android/build.gradle b/android/filament-utils-android/build.gradle new file mode 100644 index 0000000000..fe9e24877f --- /dev/null +++ b/android/filament-utils-android/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + buildToolsVersion versions.buildTools + compileSdkVersion versions.compileSdk + defaultConfig { + minSdkVersion versions.minSdk + targetSdkVersion versions.targetSdk + + externalNativeBuild { + cmake { + arguments.add("-DANDROID_PIE=ON") + arguments.add("-DANDROID_PLATFORM=android-${versions.targetSdk}".toString()) + arguments.add("-DANDROID_STL=c++_static") + arguments.add("-DFILAMENT_DIST_DIR=${filamentPath}".toString()) + cppFlags.add("-std=c++14") + if (project.hasProperty('extra_cmake_args')) { + arguments.add(extra_cmake_args) + } + } + } + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + sourceSets { + main { + jni.srcDirs "src/main/cpp" + kotlin.srcDirs += "src/main/java" + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation deps.kotlin + implementation deps.androidx.annotations + implementation project(':filament-android') + implementation project(':gltfio-android') +} + +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') + +afterEvaluate { project -> + publishing { + publications { + release(MavenPublication) { + artifactId = POM_ARTIFACT_ID + from components.release + } + } + } +} diff --git a/android/filament-utils-android/gradle.properties b/android/filament-utils-android/gradle.properties new file mode 100644 index 0000000000..9be5582656 --- /dev/null +++ b/android/filament-utils-android/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=Filament Android Utilities +POM_ARTIFACT_ID=filament-utils-android +POM_PACKAGING=aar diff --git a/android/filament-utils-android/libfilament-utils-jni.map b/android/filament-utils-android/libfilament-utils-jni.map new file mode 100644 index 0000000000..38478eb128 --- /dev/null +++ b/android/filament-utils-android/libfilament-utils-jni.map @@ -0,0 +1,4 @@ +LIBFILAMENT_UTILS { + global: Java_com_google_android_filament_*; JNI*; + local: *; +}; diff --git a/android/filament-utils-android/libfilament-utils-jni.symbols b/android/filament-utils-android/libfilament-utils-jni.symbols new file mode 100644 index 0000000000..7b079c4ad9 --- /dev/null +++ b/android/filament-utils-android/libfilament-utils-jni.symbols @@ -0,0 +1 @@ +_Java_com_google_android_filament_* diff --git a/android/filament-utils-android/src/main/AndroidManifest.xml b/android/filament-utils-android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..9709dd03d1 --- /dev/null +++ b/android/filament-utils-android/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + diff --git a/android/filament-utils-android/src/main/cpp/Bookmark.cpp b/android/filament-utils-android/src/main/cpp/Bookmark.cpp new file mode 100644 index 0000000000..884bbac373 --- /dev/null +++ b/android/filament-utils-android/src/main/cpp/Bookmark.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 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. + */ + +#include + +#include + +using namespace filament::camutils; +using namespace filament::math; + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Bookmark_nDestroyBookmark(JNIEnv*, jclass, jlong nativeBookmark) { + Bookmark* bookmark = (Bookmark*) nativeBookmark; + delete bookmark; +} diff --git a/android/filament-utils-android/src/main/cpp/Manipulator.cpp b/android/filament-utils-android/src/main/cpp/Manipulator.cpp new file mode 100644 index 0000000000..72f89586f9 --- /dev/null +++ b/android/filament-utils-android/src/main/cpp/Manipulator.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2020 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. + */ + +#include + +#include +#include + +#include + +using namespace filament::camutils; +using namespace filament::math; + +using Builder = Manipulator::Builder; + +extern "C" JNIEXPORT jlong Java_com_google_android_filament_utils_Manipulator_nCreateBuilder(JNIEnv*, jclass) { + return (jlong) new Builder {}; +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nDestroyBuilder(JNIEnv*, jclass, jlong nativeBuilder) { + Builder* builder = (Builder*) nativeBuilder; + delete builder; +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderViewport(JNIEnv*, jclass, jlong nativeBuilder, jint width, jint height) { + Builder* builder = (Builder*) nativeBuilder; + builder->viewport(width, height); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderTargetPosition(JNIEnv*, jclass, jlong nativeBuilder, jfloat x, jfloat y, jfloat z) { + Builder* builder = (Builder*) nativeBuilder; + builder->targetPosition(x, y, z); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderUpVector(JNIEnv*, jclass, jlong nativeBuilder, jfloat x, jfloat y, jfloat z) { + Builder* builder = (Builder*) nativeBuilder; + builder->upVector(x, y, z); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderZoomSpeed(JNIEnv*, jclass, jlong nativeBuilder, jfloat arg) { + Builder* builder = (Builder*) nativeBuilder; + builder->zoomSpeed(arg); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderOrbitHomePosition(JNIEnv*, jclass, jlong nativeBuilder, jfloat x, jfloat y, jfloat z) { + Builder* builder = (Builder*) nativeBuilder; + builder->orbitHomePosition(x, y, z); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderOrbitSpeed(JNIEnv*, jclass, jlong nativeBuilder, jfloat x, jfloat y) { + Builder* builder = (Builder*) nativeBuilder; + builder->orbitSpeed(x, y); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderFovDirection(JNIEnv*, jclass, jlong nativeBuilder, jint arg) { + Builder* builder = (Builder*) nativeBuilder; + builder->fovDirection((Fov) arg); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderFovDegrees(JNIEnv*, jclass, jlong nativeBuilder, jfloat arg) { + Builder* builder = (Builder*) nativeBuilder; + builder->fovDegrees(arg); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderFarPlane(JNIEnv*, jclass, jlong nativeBuilder, jfloat distance) { + Builder* builder = (Builder*) nativeBuilder; + builder->farPlane(distance); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderMapExtent(JNIEnv*, jclass, jlong nativeBuilder, jfloat width, jfloat height) { + Builder* builder = (Builder*) nativeBuilder; + builder->mapExtent(width, height); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderMapMinDistance(JNIEnv*, jclass, jlong nativeBuilder, jfloat arg) { + Builder* builder = (Builder*) nativeBuilder; + builder->mapMinDistance(arg); +} + +extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderGroundPlane(JNIEnv*, jclass, jlong nativeBuilder, jfloat a, jfloat b, jfloat c, jfloat d) { + Builder* builder = (Builder*) nativeBuilder; + builder->groundPlane(a, b, c, d); +} + +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); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_utils_Manipulator_nDestroyManipulator(JNIEnv*, jclass, jlong nativeManip) { + auto manip = (Manipulator*) nativeManip; + delete manip; +} + +extern "C" JNIEXPORT jint JNICALL Java_com_google_android_filament_utils_Manipulator_nGetMode(JNIEnv*, jclass, jlong nativeManip) { + auto manip = (Manipulator*) nativeManip; + return (int) manip->getMode(); +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_utils_Manipulator_nSetViewport(JNIEnv*, jclass, jlong nativeManip, jint width, jint height) { + auto manip = (Manipulator*) nativeManip; + manip->setViewport(width, height); +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_utils_Manipulator_nGetLookAt(JNIEnv* env, jclass, jlong nativeManip, jfloatArray eyePosition, jfloatArray targetPosition, jfloatArray upward) { + auto manip = (Manipulator*) nativeManip; + + jfloat *eye = env->GetFloatArrayElements(eyePosition, NULL); + jfloat *target = env->GetFloatArrayElements(targetPosition, NULL); + jfloat *up = env->GetFloatArrayElements(upward, NULL); + + manip->getLookAt((float3*) eye, (float3*) target, (float3*) up); + + env->ReleaseFloatArrayElements(eyePosition, eye, 0); + env->ReleaseFloatArrayElements(targetPosition, target, 0); + env->ReleaseFloatArrayElements(upward, up, 0); +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_utils_Manipulator_nRaycast(JNIEnv* env, jclass, jlong nativeManip, jint x, jint y, jfloatArray result) { + auto manip = (Manipulator*) nativeManip; + jfloat *presult = env->GetFloatArrayElements(result, NULL); + manip->raycast(x, y, (float3*) presult); + env->ReleaseFloatArrayElements(result, presult, 0); +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_utils_Manipulator_nGrabBegin(JNIEnv*, jclass, jlong nativeManip, jint x, jint y, jboolean strafe) { + auto manip = (Manipulator*) nativeManip; + manip->grabBegin(x, y, (bool) strafe); +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_utils_Manipulator_nGrabUpdate(JNIEnv*, jclass, jlong nativeManip, jint x, jint y) { + auto manip = (Manipulator*) nativeManip; + manip->grabUpdate(x, y); +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_utils_Manipulator_nGrabEnd(JNIEnv*, jclass, jlong nativeManip) { + auto manip = (Manipulator*) nativeManip; + manip->grabEnd(); +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_utils_Manipulator_nZoom(JNIEnv*, jclass, jlong nativeManip, jint x, jint y, jfloat scrolldelta) { + auto manip = (Manipulator*) nativeManip; + manip->zoom(x, y, scrolldelta); +} + +extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_utils_Manipulator_nGetCurrentBookmark(JNIEnv*, jclass, jlong nativeManip) { + auto manip = (Manipulator*) nativeManip; + return (jlong) new Bookmark(manip->getCurrentBookmark()); +} + +extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_utils_Manipulator_nGetHomeBookmark(JNIEnv*, jclass, jlong nativeManip) { + auto manip = (Manipulator*) nativeManip; + return (jlong) new Bookmark(manip->getHomeBookmark()); +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_utils_Manipulator_nJumpToBookmark(JNIEnv*, jclass, jlong nativeManip, jlong nativeBookmark) { + auto manip = (Manipulator*) nativeManip; + auto bookmark = (Bookmark*) nativeBookmark; + manip->jumpToBookmark(*bookmark); +} + diff --git a/android/gltfio-android/src/main/cpp/KtxLoader.cpp b/android/filament-utils-android/src/main/cpp/Utils.cpp similarity index 80% rename from android/gltfio-android/src/main/cpp/KtxLoader.cpp rename to android/filament-utils-android/src/main/cpp/Utils.cpp index 4e014036ac..622d3c454e 100644 --- a/android/gltfio-android/src/main/cpp/KtxLoader.cpp +++ b/android/filament-utils-android/src/main/cpp/Utils.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 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. @@ -28,8 +28,23 @@ using namespace filament; using namespace filament::math; using namespace image; +extern void registerCallbackUtils(JNIEnv*); +extern void registerNioUtils(JNIEnv*); + +jint JNI_OnLoad(JavaVM* vm, void*) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + return -1; + } + + registerCallbackUtils(env); + registerNioUtils(env); + + return JNI_VERSION_1_6; +} + extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_gltfio_KtxLoader_nCreateTexture(JNIEnv* env, jclass, +Java_com_google_android_filament_utils_KtxLoader_nCreateTexture(JNIEnv* env, jclass, jlong nativeEngine, jobject javaBuffer, jint remaining, jboolean srgb) { Engine* engine = (Engine*) nativeEngine; AutoBuffer buffer(env, javaBuffer, remaining); @@ -41,7 +56,7 @@ Java_com_google_android_filament_gltfio_KtxLoader_nCreateTexture(JNIEnv* env, jc } extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_gltfio_KtxLoader_nCreateIndirectLight(JNIEnv* env, jclass, +Java_com_google_android_filament_utils_KtxLoader_nCreateIndirectLight(JNIEnv* env, jclass, jlong nativeEngine, jobject javaBuffer, jint remaining, jboolean srgb) { Engine* engine = (Engine*) nativeEngine; AutoBuffer buffer(env, javaBuffer, remaining); @@ -64,7 +79,7 @@ Java_com_google_android_filament_gltfio_KtxLoader_nCreateIndirectLight(JNIEnv* e } extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_gltfio_KtxLoader_nCreateSkybox(JNIEnv* env, jclass, +Java_com_google_android_filament_utils_KtxLoader_nCreateSkybox(JNIEnv* env, jclass, jlong nativeEngine, jobject javaBuffer, jint remaining, jboolean srgb) { Engine* engine = (Engine*) nativeEngine; AutoBuffer buffer(env, javaBuffer, remaining); diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Bookmark.java b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Bookmark.java new file mode 100644 index 0000000000..0e98c9e978 --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Bookmark.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 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.utils; + +public class Bookmark { + private long mNativeObject; + + Bookmark(long nativeObject) { + mNativeObject = nativeObject; + } + + long getNativeObject() { + return mNativeObject; + } + + @Override + protected void finalize() throws Throwable { + nDestroyBookmark(mNativeObject); + super.finalize(); + } + + private static native void nDestroyBookmark(long nativeObject); +} diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/GestureDetector.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/GestureDetector.kt new file mode 100644 index 0000000000..470014aa7a --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/GestureDetector.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2020 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.utils + +import android.view.MotionEvent +import android.view.View +import java.util.* + +/** + * Sets up a touch listener on an Android View and forwards events to a camera manipulator. + * Supports one-touch orbit, two-touch pan, and pinch-to-zoom. + */ +class GestureDetector(private val view: View, private val manipulator: Manipulator) { + private enum class Gesture { NONE, ORBIT, PAN, ZOOM } + + // Simplified memento of MotionEvent, minimal but sufficient for our purposes. + private data class TouchEvent(var pt0: Float2, var pt1: Float2, var count: Int) { + constructor() : this(Float2(0f), Float2(0f), 0) + constructor(me: MotionEvent) : this() { + if (me.pointerCount >= 1) { + this.pt0 = Float2(me.getX(0), me.getY(0)) + this.count++ + } + if (me.pointerCount >= 2) { + this.pt1 = Float2(me.getX(1), me.getY(1)) + this.count++ + } + } + fun getPinchDistance(): Float { return distance(pt0, pt1) } + fun getTouchesMidpoint(): Float2 { return mix(pt0, pt1, 0.5f) } + } + + private var currentGesture = Gesture.NONE + private var previousEvent = TouchEvent() + private val tentativePanEvents = ArrayList() + private val tentativeOrbitEvents = ArrayList() + private val tentativeZoomEvents = ArrayList() + + private val kGestureConfidenceCount = 2 + private val kPanConfidenceDistance = 5 + private val kZoomConfidenceDistance = 10 + private val kZoomSpeed = 1f / 10f + + init { + view.setOnTouchListener func@{ _, event -> + val x = event.getX(0).toInt() + val y = view.height - event.getY(0).toInt() + when (event.actionMasked) { + MotionEvent.ACTION_MOVE -> { + + // CANCEL GESTURE DUE TO UNEXPECTED POINTER COUNT + + if ((event.pointerCount != 1 && currentGesture == Gesture.ORBIT) || + (event.pointerCount != 2 && currentGesture == Gesture.PAN) || + (event.pointerCount != 2 && currentGesture == Gesture.ZOOM)) { + endGesture() + return@func true + } + + // UPDATE EXISTING GESTURE + + if (currentGesture == Gesture.ZOOM) { + val d0 = previousEvent.getPinchDistance() + val d1 = TouchEvent(event).getPinchDistance() + manipulator.zoom(x, y, (d0 - d1) * kZoomSpeed) + previousEvent = TouchEvent(event) + return@func true + } + + if (currentGesture != Gesture.NONE) { + manipulator.grabUpdate(x, y) + return@func true + } + + // DETECT NEW GESTURE + + if (event.pointerCount == 1) { + tentativeOrbitEvents.add(TouchEvent(event)) + } + + if (event.pointerCount == 2) { + tentativePanEvents.add(TouchEvent(event)) + tentativeZoomEvents.add(TouchEvent(event)) + } + + if (isOrbitGesture()) { + manipulator.grabBegin(x, y, false) + currentGesture = Gesture.ORBIT + return@func true + } + + if (isZoomGesture()) { + currentGesture = Gesture.ZOOM + previousEvent = TouchEvent(event) + return@func true + } + + if (isPanGesture()) { + manipulator.grabBegin(x, y, true) + currentGesture = Gesture.PAN + return@func true + } + } + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + endGesture() + } + } + true + } + + } + + private fun endGesture() { + tentativePanEvents.clear() + tentativeOrbitEvents.clear() + tentativeZoomEvents.clear() + currentGesture = Gesture.NONE + manipulator.grabEnd() + } + + private fun isOrbitGesture(): Boolean { + return tentativeOrbitEvents.size > kGestureConfidenceCount + } + + private fun isPanGesture(): Boolean { + if (tentativePanEvents.size <= kGestureConfidenceCount) { + return false + } + val oldest = tentativePanEvents.first().getTouchesMidpoint() + val newest = tentativePanEvents.last().getTouchesMidpoint() + return distance(oldest, newest) > kPanConfidenceDistance + } + + private fun isZoomGesture(): Boolean { + if (tentativeZoomEvents.size <= kGestureConfidenceCount) { + return false + } + val oldest = tentativeZoomEvents.first().getPinchDistance() + val newest = tentativeZoomEvents.last().getPinchDistance() + return kotlin.math.abs(newest - oldest) > kZoomConfidenceDistance + } +} diff --git a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/KtxLoader.java b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/KtxLoader.java similarity index 97% rename from android/gltfio-android/src/main/java/com/google/android/filament/gltfio/KtxLoader.java rename to android/filament-utils-android/src/main/java/com/google/android/filament/utils/KtxLoader.java index 91a9f3cc9c..e52a1434c8 100644 --- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/KtxLoader.java +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/KtxLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.filament.gltfio; +package com.google.android.filament.utils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -25,7 +25,6 @@ import com.google.android.filament.Skybox; import com.google.android.filament.Texture; import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import java.nio.Buffer; /** diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Manipulator.java b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Manipulator.java new file mode 100644 index 0000000000..81909d39c5 --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Manipulator.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2020 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.utils; + +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Size; + +/** + * Helper that enables camera interaction similar to sketchfab or Google Maps. + * + * Clients notify the camera manipulator of various mouse or touch events, then periodically call + * its getLookAt() method so that they can adjust their camera(s). Two modes are supported: ORBIT + * and MAP. To construct a manipulator instance, the desired mode is passed into the create method. + * + * @see Bookmark + */ +public class Manipulator { + private long mNativeObject; + + private Manipulator(long nativeIndexBuffer) { + mNativeObject = nativeIndexBuffer; + } + + public enum Mode { ORBIT, MAP }; + + public enum Fov { VERTICAL, HORIZONTAL }; + + public static class Builder { + @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) + // Keep to finalize native resources + private final BuilderFinalizer mFinalizer; + private final long mNativeBuilder; + + public Builder() { + mNativeBuilder = nCreateBuilder(); + mFinalizer = new BuilderFinalizer(mNativeBuilder); + } + + /** + * Width and height of the viewing area. + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder viewport(@IntRange(from = 1) int width, @IntRange(from = 1) int height) { + nBuilderViewport(mNativeBuilder, width, height); + return this; + } + + /** + * Sets world-space position of interest, which defaults to (0,0,0). + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder targetPosition(float x, float y, float z) { + nBuilderTargetPosition(mNativeBuilder, x, y, z); + return this; + } + + /** + * Sets orientation for the home position, which defaults to (0,1,0). + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder upVector(float x, float y, float z) { + nBuilderUpVector(mNativeBuilder, x, y, z); + return this; + } + + /** + * Sets the scroll delta multiplier, which defaults to 0.01. + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder zoomSpeed(float arg) { + nBuilderZoomSpeed(mNativeBuilder, arg); + return this; + } + + /** + * Sets initial eye position in world space for ORBIT mode. + * This defaults to (0,0,1). + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder orbitHomePosition(float x, float y, float z) { + nBuilderOrbitHomePosition(mNativeBuilder, x, y, z); + return this; + } + + /** + * Sets the multiplier with viewport delta for ORBIT mode. + * This defaults to 0.01 + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder orbitSpeed(float x, float y) { + nBuilderOrbitSpeed(mNativeBuilder, x, y); + return this; + } + + /** + * Sets the FOV axis that's held constant when the viewport changes. + * This defaults to Vertical. + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder fovDirection(Fov fov) { + nBuilderFovDirection(mNativeBuilder, fov.ordinal()); + return this; + } + + /** + * Sets the full FOV (not the half-angle) in the degrees. + * This defaults to 33. + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder fovDegrees(float arg) { + nBuilderFovDegrees(mNativeBuilder, arg); + return this; + } + + /** + * Sets the distance to the far plane, which defaults to 5000. + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder farPlane(float arg) { + nBuilderFarPlane(mNativeBuilder, arg); + return this; + } + + /** + * Sets the ground plane size used to compute the home position for MAP mode. + * This defaults to 512 x 512 + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder mapExtent(float width, float height) { + nBuilderMapExtent(mNativeBuilder, width, height); + return this; + } + + /** + * Constrains the zoom-in level. Defaults to 0. + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder mapMinDistance(float arg) { + nBuilderMapMinDistance(mNativeBuilder, arg); + return this; + } + + /** + * Sets the ground plane equation used for ray casts. + * This is a plane equation as in Ax + By + Cz + D = 0. + * Defaults to (0, 0, 1, 0). + * + * @return this Builder object for chaining calls + */ + @NonNull + public Builder groundPlane(float a, float b, float c, float d) { + nBuilderGroundPlane(mNativeBuilder, a, b, c, d); + return this; + } + + /** + * Creates and returns the Manipulator object. + * + * @return the newly created Manipulator object + * + * @exception IllegalStateException if the Manipulator could not be created + * + */ + @NonNull + public Manipulator build(Mode mode) { + long nativeManipulator = nBuilderBuild(mNativeBuilder, mode.ordinal()); + if (nativeManipulator == 0) + throw new IllegalStateException("Couldn't create Manipulator"); + return new Manipulator(nativeManipulator); + } + + 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); + } + } + } + }; + + @Override + public void finalize() { + try { + super.finalize(); + } catch (Throwable t) { // Ignore + } finally { + nDestroyManipulator(mNativeObject); + } + } + + /** + * Gets the immutable mode of the manipulator. + */ + public Mode getMode() { return Mode.values()[nGetMode(mNativeObject)]; } + + /** + * Sets the viewport dimensions in terms of pixels. + * + * The manipulator uses this only in the grab and raycast methods, since + * those methods consume coordinates in viewport space. + */ + public void setViewport(int width, int height) { + nSetViewport(mNativeObject, width, height); + } + + /** + * Gets the current orthonormal basis. This is usually called once per frame. + */ + public void getLookAt( + @NonNull @Size(min = 3) float[] eyePosition, + @NonNull @Size(min = 3) float[] targetPosition, + @NonNull @Size(min = 3) float[] upward) { + nGetLookAt(mNativeObject, eyePosition, targetPosition, upward); + } + + /** + * Given a viewport coordinate, picks a point in the ground plane. + */ + @Nullable @Size(min = 3) + public float[] raycast(int x, int y) { + float[] result = new float[3]; + nRaycast(mNativeObject, x, y, result); + return result; + } + + /** + * Starts a grabbing session (i.e. the user is dragging around in the viewport). + * + * This starts a panning session in MAP mode, and start either rotating or strafing in ORBIT. + * + * @param x X-coordinate for point of interest in viewport space + * @param y Y-coordinate for point of interest in viewport space + * @param strafe ORBIT mode only; if true, starts a translation rather than a rotation. + */ + public void grabBegin(int x, int y, boolean strafe) { + nGrabBegin(mNativeObject, x, y, strafe); + } + + /** + * Updates a grabbing session. + * + * This must be called at least once between grabBegin / grabEnd to dirty the camera. + */ + public void grabUpdate(int x, int y) { + nGrabUpdate(mNativeObject, x, y); + } + + /** + * Ends a grabbing session. + */ + public void grabEnd() { + nGrabEnd(mNativeObject); + } + + /** + * Dollys the camera along the viewing direction. + * + * @param x X-coordinate for point of interest in viewport space + * @param y Y-coordinate for point of interest in viewport space + * @param scrolldelta Positive means "zoom in", negative means "zoom out" + */ + public void zoom(int x, int y, float scrolldelta) { + nZoom(mNativeObject, x, y, scrolldelta); + } + + /** + * Gets a handle that can be used to reset the manipulator back to its current position. + * + * \see jumpToBookmark + */ + public Bookmark getCurrentBookmark() { + return new Bookmark(nGetCurrentBookmark(mNativeObject)); + } + + /** + * Gets a handle that can be used to reset the manipulator back to its home position. + * + * see jumpToBookmark + */ + public Bookmark getHomeBookmark() { + return new Bookmark(nGetHomeBookmark(mNativeObject)); + } + + /** + * Sets the manipulator position and orientation back to a stashed state. + * + * \see getCurrentBookmark, getHomeBookmark + */ + public void jumpToBookmark(Bookmark bookmark) { + nJumpToBookmark(mNativeObject, bookmark.getNativeObject()); + } + + private static native long nCreateBuilder(); + private static native void nDestroyBuilder(long nativeBuilder); + private static native void nBuilderViewport(long nativeBuilder, int width, int height); + private static native void nBuilderTargetPosition(long nativeBuilder, float x, float y, float z); + private static native void nBuilderUpVector(long nativeBuilder, float x, float y, float z); + private static native void nBuilderZoomSpeed(long nativeBuilder, float arg); + private static native void nBuilderOrbitHomePosition(long nativeBuilder, float x, float y, float z); + private static native void nBuilderOrbitSpeed(long nativeBuilder, float x, float y); + private static native void nBuilderFovDirection(long nativeBuilder, int arg); + private static native void nBuilderFovDegrees(long nativeBuilder, float arg); + private static native void nBuilderFarPlane(long nativeBuilder, float distance); + private static native void nBuilderMapExtent(long nativeBuilder, float width, float height); + private static native void nBuilderMapMinDistance(long nativeBuilder, float arg); + private static native void nBuilderGroundPlane(long nativeBuilder, float a, float b, float c, float d); + private static native long nBuilderBuild(long nativeBuilder, int mode); + + private static native void nDestroyManipulator(long nativeManip); + private static native int nGetMode(long nativeManip); + private static native void nSetViewport(long nativeManip, int width, int height); + private static native void nGetLookAt(long nativeManip, float[] eyePosition, float[] targetPosition, float[] upward); + private static native void nRaycast(long nativeManip, int x, int y, float[] result); + private static native void nGrabBegin(long nativeManip, int x, int y, boolean strafe); + private static native void nGrabUpdate(long nativeManip, int x, int y); + private static native void nGrabEnd(long nativeManip); + private static native void nZoom(long nativeManip, int x, int y, float scrolldelta); + private static native long nGetCurrentBookmark(long nativeManip); + private static native long nGetHomeBookmark(long nativeManip); + private static native void nJumpToBookmark(long nativeManip, long nativeBookmark); +} 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 new file mode 100644 index 0000000000..f48ccae056 --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Matrix.kt @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2017 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. + */ + +package com.google.android.filament.utils + +import kotlin.math.* + +enum class MatrixColumn { + X, Y, Z, W +} + +data class Mat2( + var x: Float2 = Float2(x = 1.0f), + var y: Float2 = Float2(y = 1.0f)) { + constructor(m: Mat2) : this(m.x.copy(), m.y.copy()) + + companion object { + fun of(vararg a: Float): Mat2 { + require(a.size >= 4) + return Mat2( + Float2(a[0], a[2]), + Float2(a[1], a[3]) + ) + } + + fun identity() = Mat2() + } + + operator fun get(column: Int) = when(column) { + 0 -> x + 1 -> y + else -> throw IllegalArgumentException("column must be in 0..1") + } + operator fun get(column: Int, row: Int) = get(column)[row] + + operator fun get(column: MatrixColumn) = when(column) { + MatrixColumn.X -> x + MatrixColumn.Y -> y + else -> throw IllegalArgumentException("column must be X or Y") + } + operator fun get(column: MatrixColumn, row: Int) = get(column)[row] + + operator fun invoke(row: Int, column: Int) = get(column - 1)[row - 1] + operator fun invoke(row: Int, column: Int, v: Float) = set(column - 1, row - 1, v) + + operator fun set(column: Int, v: Float2) { + this[column].xy = v + } + operator fun set(column: Int, row: Int, v: Float) { + this[column][row] = v + } + + operator fun unaryMinus() = Mat2(-x, -y) + operator fun inc(): Mat2 { + x++ + y++ + return this + } + operator fun dec(): Mat2 { + x-- + y-- + return this + } + + operator fun plus(v: Float) = Mat2(x + v, y + v) + 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) + + operator fun times(m: Mat2): Mat2 { + val t = transpose(this) + return Mat2( + Float2(dot(t.x, m.x), dot(t.y, m.x)), + Float2(dot(t.x, m.y), dot(t.y, m.y)) + ) + } + + operator fun times(v: Float2): Float2 { + val t = transpose(this) + return Float2(dot(t.x, v), dot(t.y, v)) + } + + fun toFloatArray() = floatArrayOf( + x.x, y.x, + x.y, y.y + ) + + override fun toString(): String { + return """ + |${x.x} ${y.x}| + |${x.y} ${y.y}| + """.trimIndent() + } + +} + +data class Mat3( + var x: Float3 = Float3(x = 1.0f), + var y: Float3 = Float3(y = 1.0f), + var z: Float3 = Float3(z = 1.0f)) { + constructor(m: Mat3) : this(m.x.copy(), m.y.copy(), m.z.copy()) + + companion object { + fun of(vararg a: Float): Mat3 { + require(a.size >= 9) + return Mat3( + Float3(a[0], a[3], a[6]), + Float3(a[1], a[4], a[7]), + Float3(a[2], a[5], a[8]) + ) + } + + fun identity() = Mat3() + } + + operator fun get(column: Int) = when(column) { + 0 -> x + 1 -> y + 2 -> z + else -> throw IllegalArgumentException("column must be in 0..2") + } + operator fun get(column: Int, row: Int) = get(column)[row] + + operator fun get(column: MatrixColumn) = when(column) { + MatrixColumn.X -> x + MatrixColumn.Y -> y + MatrixColumn.Z -> z + else -> throw IllegalArgumentException("column must be X, Y or Z") + } + operator fun get(column: MatrixColumn, row: Int) = get(column)[row] + + operator fun invoke(row: Int, column: Int) = get(column - 1)[row - 1] + operator fun invoke(row: Int, column: Int, v: Float) = set(column - 1, row - 1, v) + + operator fun set(column: Int, v: Float3) { + this[column].xyz = v + } + operator fun set(column: Int, row: Int, v: Float) { + this[column][row] = v + } + + operator fun unaryMinus() = Mat3(-x, -y, -z) + operator fun inc(): Mat3 { + x++ + y++ + z++ + return this + } + operator fun dec(): Mat3 { + x-- + y-- + z-- + return this + } + + operator fun plus(v: Float) = Mat3(x + v, y + v, z + v) + 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) + + operator fun times(m: Mat3): Mat3 { + val t = transpose(this) + return Mat3( + Float3(dot(t.x, m.x), dot(t.y, m.x), dot(t.z, m.x)), + Float3(dot(t.x, m.y), dot(t.y, m.y), dot(t.z, m.y)), + Float3(dot(t.x, m.z), dot(t.y, m.z), dot(t.z, m.z)) + ) + } + + operator fun times(v: Float3): Float3 { + val t = transpose(this) + return Float3(dot(t.x, v), dot(t.y, v), dot(t.z, v)) + } + + fun toFloatArray() = floatArrayOf( + x.x, y.x, z.x, + x.y, y.y, z.y, + x.z, y.z, z.z + ) + + override fun toString(): String { + return """ + |${x.x} ${y.x} ${z.x}| + |${x.y} ${y.y} ${z.y}| + |${x.z} ${y.z} ${z.z}| + """.trimIndent() + } +} + +data class Mat4( + var x: Float4 = Float4(x = 1.0f), + var y: Float4 = Float4(y = 1.0f), + var z: Float4 = Float4(z = 1.0f), + var w: Float4 = Float4(w = 1.0f)) { + constructor(right: Float3, up: Float3, forward: Float3, position: Float3 = Float3()) : + this(Float4(right), Float4(up), Float4(forward), Float4(position, 1.0f)) + 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( + Float4(a[0], a[4], a[8], a[12]), + Float4(a[1], a[5], a[9], a[13]), + Float4(a[2], a[6], a[10], a[14]), + Float4(a[3], a[7], a[11], a[15]) + ) + } + + fun identity() = Mat4() + } + + inline var right: Float3 + get() = x.xyz + set(value) { + x.xyz = value + } + inline var up: Float3 + get() = y.xyz + set(value) { + y.xyz = value + } + inline var forward: Float3 + get() = z.xyz + set(value) { + z.xyz = value + } + inline var position: Float3 + get() = w.xyz + set(value) { + w.xyz = value + } + + inline val scale: Float3 + get() = Float3(length(x.xyz), length(y.xyz), length(z.xyz)) + inline val translation: Float3 + get() = w.xyz + val rotation: Float3 + get() { + val x = normalize(right) + val y = normalize(up) + val z = normalize(forward) + + return when { + z.y <= -1.0f -> Float3(degrees(-HALF_PI), 0.0f, degrees(atan2( x.z, y.z))) + z.y >= 1.0f -> Float3(degrees( HALF_PI), 0.0f, degrees(atan2(-x.z, -y.z))) + else -> Float3( + degrees(-asin(z.y)), degrees(-atan2(z.x, z.z)), degrees(atan2( x.y, y.y))) + } + } + + inline val upperLeft: Mat3 + get() = Mat3(x.xyz, y.xyz, z.xyz) + + operator fun get(column: Int) = when(column) { + 0 -> x + 1 -> y + 2 -> z + 3 -> w + else -> throw IllegalArgumentException("column must be in 0..3") + } + operator fun get(column: Int, row: Int) = get(column)[row] + + operator fun get(column: MatrixColumn) = when(column) { + MatrixColumn.X -> x + MatrixColumn.Y -> y + MatrixColumn.Z -> z + MatrixColumn.W -> w + } + operator fun get(column: MatrixColumn, row: Int) = get(column)[row] + + operator fun invoke(row: Int, column: Int) = get(column - 1)[row - 1] + operator fun invoke(row: Int, column: Int, v: Float) = set(column - 1, row - 1, v) + + operator fun set(column: Int, v: Float4) { + this[column].xyzw = v + } + operator fun set(column: Int, row: Int, v: Float) { + this[column][row] = v + } + + operator fun unaryMinus() = Mat4(-x, -y, -z, -w) + operator fun inc(): Mat4 { + x++ + y++ + z++ + w++ + return this + } + operator fun dec(): Mat4 { + x-- + y-- + z-- + w-- + return this + } + + operator fun plus(v: Float) = Mat4(x + v, y + v, z + v, w + v) + 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) + + operator fun times(m: Mat4): Mat4 { + val t = transpose(this) + return Mat4( + Float4(dot(t.x, m.x), dot(t.y, m.x), dot(t.z, m.x), dot(t.w, m.x)), + Float4(dot(t.x, m.y), dot(t.y, m.y), dot(t.z, m.y), dot(t.w, m.y)), + Float4(dot(t.x, m.z), dot(t.y, m.z), dot(t.z, m.z), dot(t.w, m.z)), + Float4(dot(t.x, m.w), dot(t.y, m.w), dot(t.z, m.w), dot(t.w, m.w)) + ) + } + + operator fun times(v: Float4): Float4 { + val t = transpose(this) + return Float4(dot(t.x, v), dot(t.y, v), dot(t.z, v), dot(t.w, v)) + } + + fun toFloatArray() = floatArrayOf( + x.x, y.x, z.x, w.x, + x.y, y.y, z.y, w.y, + x.z, y.z, z.z, w.z, + x.w, y.w, z.w, w.w + ) + + override fun toString(): String { + return """ + |${x.x} ${y.x} ${z.x} ${w.x}| + |${x.y} ${y.y} ${z.y} ${w.y}| + |${x.z} ${y.z} ${z.z} ${w.z}| + |${x.w} ${y.w} ${z.w} ${w.w}| + """.trimIndent() + } +} + +fun transpose(m: Mat2) = Mat2( + Float2(m.x.x, m.y.x), + Float2(m.x.y, m.y.y) +) + +fun transpose(m: Mat3) = Mat3( + Float3(m.x.x, m.y.x, m.z.x), + Float3(m.x.y, m.y.y, m.z.y), + Float3(m.x.z, m.y.z, m.z.z) +) +fun inverse(m: Mat3): Mat3 { + val a = m.x.x + val b = m.x.y + val c = m.x.z + val d = m.y.x + val e = m.y.y + val f = m.y.z + val g = m.z.x + val h = m.z.y + val i = m.z.z + + val A = e * i - f * h + val B = f * g - d * i + val C = d * h - e * g + + val det = a * A + b * B + c * C + + return Mat3.of( + A / det, B / det, C / det, + (c * h - b * i) / det, (a * i - c * g) / det, (b * g - a * h) / det, + (b * f - c * e) / det, (c * d - a * f) / det, (a * e - b * d) / det + ) +} + +fun transpose(m: Mat4) = Mat4( + Float4(m.x.x, m.y.x, m.z.x, m.w.x), + Float4(m.x.y, m.y.y, m.z.y, m.w.y), + Float4(m.x.z, m.y.z, m.z.z, m.w.z), + Float4(m.x.w, m.y.w, m.z.w, m.w.w) +) +fun inverse(m: Mat4): Mat4 { + val result = Mat4() + + var pair0 = m.z.z * m.w.w + var pair1 = m.w.z * m.z.w + var pair2 = m.y.z * m.w.w + var pair3 = m.w.z * m.y.w + var pair4 = m.y.z * m.z.w + var pair5 = m.z.z * m.y.w + var pair6 = m.x.z * m.w.w + var pair7 = m.w.z * m.x.w + var pair8 = m.x.z * m.z.w + var pair9 = m.z.z * m.x.w + var pair10 = m.x.z * m.y.w + var pair11 = m.y.z * m.x.w + + result.x.x = pair0 * m.y.y + pair3 * m.z.y + pair4 * m.w.y + result.x.x -= pair1 * m.y.y + pair2 * m.z.y + pair5 * m.w.y + result.x.y = pair1 * m.x.y + pair6 * m.z.y + pair9 * m.w.y + result.x.y -= pair0 * m.x.y + pair7 * m.z.y + pair8 * m.w.y + result.x.z = pair2 * m.x.y + pair7 * m.y.y + pair10 * m.w.y + result.x.z -= pair3 * m.x.y + pair6 * m.y.y + pair11 * m.w.y + result.x.w = pair5 * m.x.y + pair8 * m.y.y + pair11 * m.z.y + result.x.w -= pair4 * m.x.y + pair9 * m.y.y + pair10 * m.z.y + result.y.x = pair1 * m.y.x + pair2 * m.z.x + pair5 * m.w.x + result.y.x -= pair0 * m.y.x + pair3 * m.z.x + pair4 * m.w.x + result.y.y = pair0 * m.x.x + pair7 * m.z.x + pair8 * m.w.x + result.y.y -= pair1 * m.x.x + pair6 * m.z.x + pair9 * m.w.x + result.y.z = pair3 * m.x.x + pair6 * m.y.x + pair11 * m.w.x + result.y.z -= pair2 * m.x.x + pair7 * m.y.x + pair10 * m.w.x + result.y.w = pair4 * m.x.x + pair9 * m.y.x + pair10 * m.z.x + result.y.w -= pair5 * m.x.x + pair8 * m.y.x + pair11 * m.z.x + + pair0 = m.z.x * m.w.y + pair1 = m.w.x * m.z.y + pair2 = m.y.x * m.w.y + pair3 = m.w.x * m.y.y + pair4 = m.y.x * m.z.y + pair5 = m.z.x * m.y.y + pair6 = m.x.x * m.w.y + pair7 = m.w.x * m.x.y + pair8 = m.x.x * m.z.y + pair9 = m.z.x * m.x.y + pair10 = m.x.x * m.y.y + pair11 = m.y.x * m.x.y + + result.z.x = pair0 * m.y.w + pair3 * m.z.w + pair4 * m.w.w + result.z.x -= pair1 * m.y.w + pair2 * m.z.w + pair5 * m.w.w + result.z.y = pair1 * m.x.w + pair6 * m.z.w + pair9 * m.w.w + result.z.y -= pair0 * m.x.w + pair7 * m.z.w + pair8 * m.w.w + result.z.z = pair2 * m.x.w + pair7 * m.y.w + pair10 * m.w.w + result.z.z -= pair3 * m.x.w + pair6 * m.y.w + pair11 * m.w.w + result.z.w = pair5 * m.x.w + pair8 * m.y.w + pair11 * m.z.w + result.z.w -= pair4 * m.x.w + pair9 * m.y.w + pair10 * m.z.w + result.w.x = pair2 * m.z.z + pair5 * m.w.z + pair1 * m.y.z + result.w.x -= pair4 * m.w.z + pair0 * m.y.z + pair3 * m.z.z + result.w.y = pair8 * m.w.z + pair0 * m.x.z + pair7 * m.z.z + result.w.y -= pair6 * m.z.z + pair9 * m.w.z + pair1 * m.x.z + result.w.z = pair6 * m.y.z + pair11 * m.w.z + pair3 * m.x.z + result.w.z -= pair10 * m.w.z + pair2 * m.x.z + pair7 * m.y.z + result.w.w = pair10 * m.z.z + pair4 * m.x.z + pair9 * m.y.z + result.w.w -= pair8 * m.y.z + pair11 * m.z.z + pair5 * m.x.z + + val determinant = m.x.x * result.x.x + m.y.x * result.x.y + m.z.x * result.x.z + m.w.x * result.x.w + + return result / determinant +} + +fun scale(s: Float3) = Mat4(Float4(x = s.x), Float4(y = s.y), Float4(z = s.z)) +fun scale(m: Mat4) = scale(m.scale) + +fun translation(t: Float3) = Mat4(w = Float4(t, 1.0f)) +fun translation(m: Mat4) = translation(m.translation) + +fun rotation(m: Mat4) = Mat4(normalize(m.right), normalize(m.up), normalize(m.forward)) +fun rotation(d: Float3): Mat4 { + val r = transform(d, ::radians) + val c = transform(r, { x -> cos(x) }) + val s = transform(r, { x -> sin(x) }) + + return Mat4.of( + c.y * c.z, -c.x * s.z + s.x * s.y * c.z, s.x * s.z + c.x * s.y * c.z, 0.0f, + c.y * s.z, c.x * c.z + s.x * s.y * s.z, -s.x * c.z + c.x * s.y * s.z, 0.0f, + -s.y , s.x * c.y , c.x * c.y , 0.0f, + 0.0f , 0.0f , 0.0f , 1.0f + ) +} +fun rotation(axis: Float3, angle: Float): Mat4 { + val x = axis.x + val y = axis.y + val z = axis.z + + val r = radians(angle) + val c = cos(r) + val s = sin(r) + val d = 1.0f - c + + return Mat4.of( + x * x * d + c , x * y * d - z * s, x * z * d + y * s, 0.0f, + y * x * d + z * s, y * y * d + c , y * z * d - x * s, 0.0f, + z * x * d - y * s, z * y * d + x * s, z * z * d + c , 0.0f, + 0.0f , 0.0f , 0.0f , 1.0f + ) +} + +fun normal(m: Mat4) = scale(1.0f / Float3(length2(m.right), length2(m.up), length2(m.forward))) * m + +fun lookAt(eye: Float3, target: Float3, up: Float3 = Float3(z = 1.0f)): Mat4 { + return lookTowards(eye, target - eye, up) +} + +fun lookTowards(eye: Float3, forward: Float3, up: Float3 = Float3(z = 1.0f)): Mat4 { + val f = normalize(forward) + val r = normalize(f x up) + val u = normalize(r x f) + return Mat4(Float4(r), Float4(u), Float4(f), Float4(eye, 1.0f)) +} + +fun perspective(fov: Float, ratio: Float, near: Float, far: Float): Mat4 { + val t = 1.0f / tan(radians(fov) * 0.5f) + val a = (far + near) / (far - near) + val b = (2.0f * far * near) / (far - near) + val c = t / ratio + return Mat4(Float4(x = c), Float4(y = t), Float4(z = a, w = 1.0f), Float4(z = -b)) +} + +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) +) + diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Ray.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Ray.kt new file mode 100644 index 0000000000..2cdb8c06df --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Ray.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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. + */ + +package com.google.android.filament.utils + +data class Ray(var origin: Float3 = Float3(), var direction: Float3) + +fun pointAt(r: Ray, t: Float) = r.origin + r.direction * t 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 new file mode 100644 index 0000000000..5e878b25bf --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Scalar.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 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. + */ + +@file:Suppress("NOTHING_TO_INLINE") + +package com.google.android.filament.utils + +const val FPI = 3.1415926536f +const val HALF_PI = FPI * 0.5f +const val TWO_PI = FPI * 2.0f +const val FOUR_PI = FPI * 4.0f +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) + +inline fun saturate(x: Float) = clamp(x, 0.0f, 1.0f) + +inline fun mix(a: Float, b: Float, x: Float) = a * (1.0f - x) + b * x + +inline fun degrees(v: Float) = v * (180.0f * INV_PI) + +inline fun radians(v: Float) = v * (FPI / 180.0f) + +inline fun fract(v: Float) = v % 1 + +inline fun sqr(v: Float) = v * v + +inline fun pow(x: Float, y: Float) = StrictMath.pow(x.toDouble(), y.toDouble()).toFloat() diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Utils.java b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Utils.java new file mode 100644 index 0000000000..fbd84419f0 --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Utils.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 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.utils; + +public class Utils { + + /** + * Initializes the utils JNI layer. Must be called before using any utils functionality. + */ + public static void init() { + System.loadLibrary("filament-utils-jni"); + } +} 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 new file mode 100644 index 0000000000..fce6cb9568 --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/Vector.kt @@ -0,0 +1,1299 @@ +/* + * Copyright (C) 2017 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. + */ + +@file:Suppress("NOTHING_TO_INLINE") + +package com.google.android.filament.utils + +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sqrt + +enum class VectorComponent { + X, Y, Z, W, + R, G, B, A, + S, T, P, Q +} + +data class Float2(var x: Float = 0.0f, var y: Float = 0.0f) { + constructor(v: Float) : this(v, v) + constructor(v: Float2) : this(v.x, v.y) + + inline var r: Float + get() = x + set(value) { + x = value + } + inline var g: Float + get() = y + set(value) { + y = value + } + + inline var s: Float + get() = x + set(value) { + x = value + } + inline var t: Float + get() = y + set(value) { + y = value + } + + inline var xy: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Float2 + get() = Float2(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): Float2 { + return Float2(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) = Float2(get(index1), get(index2)) + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Float) = 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: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set(index: VectorComponent, v: Float) = 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: Float) { + set(index1, v) + set(index2, v) + } + + operator fun unaryMinus() = Float2(-x, -y) + operator fun inc(): Float2 { + x += 1.0f + y += 1.0f + return this + } + + operator fun dec(): Float2 { + x -= 1.0f + y -= 1.0f + return this + } + + inline operator fun plus(v: Float) = Float2(x + v, y + v) + 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 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 transform(block: (Float) -> Float): Float2 { + x = block(x) + y = block(y) + return this + } +} + +data class Float3(var x: Float = 0.0f, var y: Float = 0.0f, var z: Float = 0.0f) { + constructor(v: Float) : this(v, v, v) + constructor(v: Float2, z: Float = 0.0f) : this(v.x, v.y, z) + constructor(v: Float3) : this(v.x, v.y, v.z) + + inline var r: Float + get() = x + set(value) { + x = value + } + inline var g: Float + get() = y + set(value) { + y = value + } + inline var b: Float + get() = z + set(value) { + z = value + } + + inline var s: Float + get() = x + set(value) { + x = value + } + inline var t: Float + get() = y + set(value) { + y = value + } + inline var p: Float + get() = z + set(value) { + z = value + } + + inline var xy: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + + inline var rgb: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var xyz: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var stp: Float3 + get() = Float3(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): Float2 { + return Float2(get(index1), get(index2)) + } + operator fun get( + index1: VectorComponent, index2: VectorComponent, index3: VectorComponent): Float3 { + return Float3(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) = Float2(get(index1), get(index2)) + operator fun get(index1: Int, index2: Int, index3: Int): Float3 { + return Float3(get(index1), get(index2), get(index3)) + } + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Float) = 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: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index: VectorComponent, v: Float) = 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: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent, + v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun unaryMinus() = Float3(-x, -y, -z) + operator fun inc(): Float3 { + x += 1.0f + y += 1.0f + z += 1.0f + return this + } + + operator fun dec(): Float3 { + x -= 1.0f + y -= 1.0f + z -= 1.0f + return this + } + + inline operator fun plus(v: Float) = Float3(x + v, y + v, z + v) + 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 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) + inline operator fun times(v: Float2) = Float3(x * v.x, y * v.y, z) + inline operator fun div(v: Float2) = Float3(x / v.x, y / v.y, z) + + inline operator fun plus(v: Float3) = Float3(x + v.x, y + v.y, z + v.z) + 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 transform(block: (Float) -> Float): Float3 { + x = block(x) + y = block(y) + z = block(z) + return this + } +} + +data class Float4( + var x: Float = 0.0f, + var y: Float = 0.0f, + var z: Float = 0.0f, + var w: Float = 0.0f) { + constructor(v: Float) : this(v, v, v, v) + constructor(v: Float2, z: Float = 0.0f, w: Float = 0.0f) : this(v.x, v.y, z, w) + constructor(v: Float3, w: Float = 0.0f) : this(v.x, v.y, v.z, w) + constructor(v: Float4) : this(v.x, v.y, v.z, v.w) + + inline var r: Float + get() = x + set(value) { + x = value + } + inline var g: Float + get() = y + set(value) { + y = value + } + inline var b: Float + get() = z + set(value) { + z = value + } + inline var a: Float + get() = w + set(value) { + w = value + } + + inline var s: Float + get() = x + set(value) { + x = value + } + inline var t: Float + get() = y + set(value) { + y = value + } + inline var p: Float + get() = z + set(value) { + z = value + } + inline var q: Float + get() = w + set(value) { + w = value + } + + inline var xy: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Float2 + get() = Float2(x, y) + set(value) { + x = value.x + y = value.y + } + + inline var rgb: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var xyz: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var stp: Float3 + get() = Float3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + + inline var rgba: Float4 + get() = Float4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + inline var xyzw: Float4 + get() = Float4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + inline var stpq: Float4 + get() = Float4(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): Float2 { + return Float2(get(index1), get(index2)) + } + operator fun get( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent): Float3 { + return Float3(get(index1), get(index2), get(index3)) + } + operator fun get( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent, + index4: VectorComponent): Float4 { + return Float4(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) = Float2(get(index1), get(index2)) + operator fun get(index1: Int, index2: Int, index3: Int): Float3 { + return Float3(get(index1), get(index2), get(index3)) + } + operator fun get(index1: Int, index2: Int, index3: Int, index4: Int): Float4 { + return Float4(get(index1), get(index2), get(index3), get(index4)) + } + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Float) = 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: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, index4: Int, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + set(index4, v) + } + + operator fun set(index: VectorComponent, v: Float) = 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: Float) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, + v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, + index3: VectorComponent, index4: VectorComponent, v: Float) { + set(index1, v) + set(index2, v) + set(index3, v) + set(index4, v) + } + + operator fun unaryMinus() = Float4(-x, -y, -z, -w) + operator fun inc(): Float4 { + x += 1.0f + y += 1.0f + z += 1.0f + w += 1.0f + return this + } + + operator fun dec(): Float4 { + x -= 1.0f + y -= 1.0f + z -= 1.0f + w -= 1.0f + return this + } + + inline operator fun plus(v: Float) = Float4(x + v, y + v, z + v, w + v) + 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 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) + inline operator fun times(v: Float2) = Float4(x * v.x, y * v.y, z, w) + inline operator fun div(v: Float2) = Float4(x / v.x, y / v.y, z, w) + + inline operator fun plus(v: Float3) = Float4(x + v.x, y + v.y, z + v.z, w) + inline operator fun minus(v: Float3) = Float4(x - v.x, y - v.y, z - v.z, w) + inline operator fun times(v: Float3) = Float4(x * v.x, y * v.y, z * v.z, w) + inline operator fun div(v: Float3) = Float4(x / v.x, y / v.y, z / v.z, w) + + inline operator fun plus(v: Float4) = Float4(x + v.x, y + v.y, z + v.z, w + v.w) + 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 transform(block: (Float) -> Float): Float4 { + x = block(x) + y = block(y) + z = block(z) + w = block(w) + return this + } +} + +inline operator fun Float.plus(v: Float2) = Float2(this + v.x, this + v.y) +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 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 +inline fun distance(a: Float2, b: Float2) = length(a - b) +inline fun dot(a: Float2, b: Float2) = a.x * b.x + a.y * b.y +fun normalize(v: Float2): Float2 { + val l = 1.0f / length(v) + return Float2(v.x * l, v.y * l) +} + +inline fun reflect(i: Float2, n: Float2) = i - 2.0f * dot(n, i) * n +fun refract(i: Float2, n: Float2, eta: Float): Float2 { + val d = dot(n, i) + val k = 1.0f - eta * eta * (1.0f - sqr(d)) + return if (k < 0.0f) Float2(0.0f) else eta * i - (eta * d + sqrt(k)) * n +} + +inline fun clamp(v: Float2, min: Float, max: Float): Float2 { + return Float2( + clamp(v.x, min, max), + clamp(v.y, min, max)) +} + +inline fun clamp(v: Float2, min: Float2, max: Float2): Float2 { + return Float2( + clamp(v.x, min.x, max.x), + clamp(v.y, min.y, max.y)) +} + +inline fun mix(a: Float2, b: Float2, x: Float): Float2 { + return Float2( + mix(a.x, b.x, x), + mix(a.y, b.y, x)) +} + +inline fun mix(a: Float2, b: Float2, x: Float2): Float2 { + return Float2( + mix(a.x, b.x, x.x), + mix(a.y, b.y, x.y)) +} + +inline fun min(v: Float2) = min(v.x, v.y) +inline fun min(a: Float2, b: Float2) = Float2(min(a.x, b.x), min(a.y, b.y)) +inline fun max(v: Float2) = max(v.x, v.y) +inline fun max(a: Float2, b: Float2) = Float2(max(a.x, b.x), max(a.y, b.y)) + +inline fun transform(v: Float2, block: (Float) -> Float) = v.copy().transform(block) + +inline fun lessThan(a: Float2, b: Float) = Bool2(a.x < b, a.y < b) +inline fun lessThan(a: Float2, b: Float2) = Bool2(a.x < b.x, a.y < b.y) +inline fun lessThanEqual(a: Float2, b: Float) = Bool2(a.x <= b, a.y <= b) +inline fun lessThanEqual(a: Float2, b: Float2) = Bool2(a.x <= b.x, a.y <= b.y) +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 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) +inline infix fun Float2.lte(b: Float) = Bool2(x <= b, y <= b) +inline infix fun Float2.lte(b: Float2) = Bool2(x <= b.x, y <= b.y) +inline infix fun Float2.gt(b: Float) = Bool2(x > b, y > b) +inline infix fun Float2.gt(b: Float2) = Bool2(x > b.x, y > b.y) +inline infix fun Float2.gte(b: Float) = Bool2(x >= b, y >= b) +inline infix fun Float2.gte(b: Float2) = Bool2(x >= b.x, y >= b.y) +inline infix fun Float2.eq(b: Float) = Bool2(x == b, y == b) +inline infix fun Float2.eq(b: Float2) = Bool2(x == b.x, y == b.y) +inline infix fun Float2.neq(b: Float) = Bool2(x != b, y != b) +inline infix fun Float2.neq(b: Float2) = Bool2(x != b.x, y != b.y) + +inline fun any(v: Bool2) = v.x || v.y +inline fun all(v: Bool2) = v.x && v.y + +inline operator fun Float.plus(v: Float3) = Float3(this + v.x, this + v.y, this + v.z) +inline operator fun Float.minus(v: Float3) = Float3(this - v.x, this - v.y, this - v.z) +inline operator fun Float.times(v: Float3) = Float3(this * v.x, this * v.y, this * v.z) +inline operator fun Float.div(v: Float3) = Float3(this / v.x, this / v.y, this / v.z) + +inline fun abs(v: Float3) = Float3(abs(v.x), abs(v.y), abs(v.z)) +inline fun length(v: Float3) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z) +inline fun length2(v: Float3) = v.x * v.x + v.y * v.y + v.z * v.z +inline fun distance(a: Float3, b: Float3) = length(a - b) +inline fun dot(a: Float3, b: Float3) = a.x * b.x + a.y * b.y + a.z * b.z +inline fun cross(a: Float3, b: Float3): Float3 { + return Float3(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 Float3.x(v: Float3): Float3 { + return Float3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x) +} +fun normalize(v: Float3): Float3 { + val l = 1.0f / length(v) + return Float3(v.x * l, v.y * l, v.z * l) +} + +inline fun reflect(i: Float3, n: Float3) = i - 2.0f * dot(n, i) * n +fun refract(i: Float3, n: Float3, eta: Float): Float3 { + val d = dot(n, i) + val k = 1.0f - eta * eta * (1.0f - sqr(d)) + return if (k < 0.0f) Float3(0.0f) else eta * i - (eta * d + sqrt(k)) * n +} + +inline fun clamp(v: Float3, min: Float, max: Float): Float3 { + return Float3( + clamp(v.x, min, max), + clamp(v.y, min, max), + clamp(v.z, min, max)) +} + +inline fun clamp(v: Float3, min: Float3, max: Float3): Float3 { + return Float3( + 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: Float3, b: Float3, x: Float): Float3 { + return Float3( + mix(a.x, b.x, x), + mix(a.y, b.y, x), + mix(a.z, b.z, x)) +} + +inline fun mix(a: Float3, b: Float3, x: Float3): Float3 { + return Float3( + 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: Float3) = min(v.x, min(v.y, v.z)) +inline fun min(a: Float3, b: Float3) = Float3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)) +inline fun max(v: Float3) = max(v.x, max(v.y, v.z)) +inline fun max(a: Float3, b: Float3) = Float3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)) + +inline fun transform(v: Float3, block: (Float) -> Float) = v.copy().transform(block) + +inline fun lessThan(a: Float3, b: Float) = Bool3(a.x < b, a.y < b, a.z < b) +inline fun lessThan(a: Float3, b: Float3) = Bool3(a.x < b.x, a.y < b.y, a.z < b.z) +inline fun lessThanEqual(a: Float3, b: Float) = Bool3(a.x <= b, a.y <= b, a.z <= b) +inline fun lessThanEqual(a: Float3, b: Float3) = Bool3(a.x <= b.x, a.y <= b.y, a.z <= b.z) +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 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) +inline infix fun Float3.lte(b: Float) = Bool3(x <= b, y <= b, z <= b) +inline infix fun Float3.lte(b: Float3) = Bool3(x <= b.x, y <= b.y, z <= b.z) +inline infix fun Float3.gt(b: Float) = Bool3(x > b, y > b, z > b) +inline infix fun Float3.gt(b: Float3) = Bool3(x > b.x, y > b.y, z > b.z) +inline infix fun Float3.gte(b: Float) = Bool3(x >= b, y >= b, z >= b) +inline infix fun Float3.gte(b: Float3) = Bool3(x >= b.x, y >= b.y, z >= b.z) +inline infix fun Float3.eq(b: Float) = Bool3(x == b, y == b, z == b) +inline infix fun Float3.eq(b: Float3) = Bool3(x == b.x, y == b.y, z == b.z) +inline infix fun Float3.neq(b: Float) = Bool3(x != b, y != b, z != b) +inline infix fun Float3.neq(b: Float3) = Bool3(x != b.x, y != b.y, z != b.z) + +inline fun any(v: Bool3) = v.x || v.y || v.z +inline fun all(v: Bool3) = v.x && v.y && v.z + +inline operator fun Float.plus(v: Float4) = Float4(this + v.x, this + v.y, this + v.z, this + v.w) +inline operator fun Float.minus(v: Float4) = Float4(this - v.x, this - v.y, this - v.z, this - v.w) +inline operator fun Float.times(v: Float4) = Float4(this * v.x, this * v.y, this * v.z, this * v.w) +inline operator fun Float.div(v: Float4) = Float4(this / v.x, this / v.y, this / v.z, this / v.w) + +inline fun abs(v: Float4) = Float4(abs(v.x), abs(v.y), abs(v.z), abs(v.w)) +inline fun length(v: Float4) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w) +inline fun length2(v: Float4) = v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w +inline fun distance(a: Float4, b: Float4) = length(a - b) +inline fun dot(a: Float4, b: Float4) = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w +fun normalize(v: Float4): Float4 { + val l = 1.0f / length(v) + return Float4(v.x * l, v.y * l, v.z * l, v.w * l) +} + +inline fun clamp(v: Float4, min: Float, max: Float): Float4 { + return Float4( + clamp(v.x, min, max), + clamp(v.y, min, max), + clamp(v.z, min, max), + clamp(v.w, min, max)) +} + +inline fun clamp(v: Float4, min: Float4, max: Float4): Float4 { + return Float4( + 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: Float4, b: Float4, x: Float): Float4 { + return Float4( + 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: Float4, b: Float4, x: Float4): Float4 { + return Float4( + 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: Float4) = min(v.x, min(v.y, min(v.z, v.w))) +inline fun min(a: Float4, b: Float4): Float4 { + return Float4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)) +} +inline fun max(v: Float4) = max(v.x, max(v.y, max(v.z, v.w))) +inline fun max(a: Float4, b: Float4): Float4 { + return Float4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)) +} + +inline fun transform(v: Float4, block: (Float) -> Float) = v.copy().transform(block) + +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 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 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) +inline infix fun Float4.gt(b: Float) = Bool4(x > b, y > b, z > b, w > b) +inline infix fun Float4.gt(b: Float4) = Bool4(x > b.x, y > b.y, z > b.z, w > b.w) +inline infix fun Float4.gte(b: Float) = Bool4(x >= b, y >= b, z >= b, w >= b) +inline infix fun Float4.gte(b: Float4) = Bool4(x >= b.x, y >= b.y, z >= b.z, w >= b.w) +inline infix fun Float4.eq(b: Float) = Bool4(x == b, y == b, z == b, w == b) +inline infix fun Float4.eq(b: Float4) = Bool4(x == b.x, y == b.y, z == b.z, w == b.w) +inline infix fun Float4.neq(b: Float) = Bool4(x != b, y != b, z != b, w != b) +inline infix fun Float4.neq(b: Float4) = Bool4(x != b.x, y != b.y, z != b.z, w != b.w) + +inline fun any(v: Bool4) = v.x || v.y || v.z || v.w +inline fun all(v: Bool4) = v.x && v.y && v.z && v.w + +data class Bool2(var x: Boolean = false, var y: Boolean = false) { + constructor(v: Bool2) : this(v.x, v.y) + + inline var r: Boolean + get() = x + set(value) { + x = value + } + inline var g: Boolean + get() = y + set(value) { + y = value + } + + inline var s: Boolean + get() = x + set(value) { + x = value + } + inline var t: Boolean + get() = y + set(value) { + y = value + } + + inline var xy: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Bool2 + get() = Bool2(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): Bool2 { + return Bool2(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) = Bool2(get(index1), get(index2)) + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Boolean) = 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: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set(index: VectorComponent, v: Boolean) = 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: Boolean) { + set(index1, v) + set(index2, v) + } +} + +data class Bool3(var x: Boolean = false, var y: Boolean = false, var z: Boolean = false) { + constructor(v: Bool2, z: Boolean = false) : this(v.x, v.y, z) + constructor(v: Bool3) : this(v.x, v.y, v.z) + + inline var r: Boolean + get() = x + set(value) { + x = value + } + inline var g: Boolean + get() = y + set(value) { + y = value + } + inline var b: Boolean + get() = z + set(value) { + z = value + } + + inline var s: Boolean + get() = x + set(value) { + x = value + } + inline var t: Boolean + get() = y + set(value) { + y = value + } + inline var p: Boolean + get() = z + set(value) { + z = value + } + + inline var xy: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + + inline var rgb: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var xyz: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var stp: Bool3 + get() = Bool3(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): Bool2 { + return Bool2(get(index1), get(index2)) + } + operator fun get( + index1: VectorComponent, index2: VectorComponent, index3: VectorComponent): Bool3 { + return Bool3(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) = Bool2(get(index1), get(index2)) + operator fun get(index1: Int, index2: Int, index3: Int): Bool3 { + return Bool3(get(index1), get(index2), get(index3)) + } + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Boolean) = 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: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index: VectorComponent, v: Boolean) = 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: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent, + v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + } +} + +data class Bool4( + var x: Boolean = false, + var y: Boolean = false, + var z: Boolean = false, + var w: Boolean = false) { + constructor(v: Bool2, z: Boolean = false, w: Boolean = false) : this(v.x, v.y, z, w) + constructor(v: Bool3, w: Boolean = false) : this(v.x, v.y, v.z, w) + constructor(v: Bool4) : this(v.x, v.y, v.z, v.w) + + inline var r: Boolean + get() = x + set(value) { + x = value + } + inline var g: Boolean + get() = y + set(value) { + y = value + } + inline var b: Boolean + get() = z + set(value) { + z = value + } + inline var a: Boolean + get() = w + set(value) { + w = value + } + + inline var s: Boolean + get() = x + set(value) { + x = value + } + inline var t: Boolean + get() = y + set(value) { + y = value + } + inline var p: Boolean + get() = z + set(value) { + z = value + } + inline var q: Boolean + get() = w + set(value) { + w = value + } + + inline var xy: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var rg: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + inline var st: Bool2 + get() = Bool2(x, y) + set(value) { + x = value.x + y = value.y + } + + inline var rgb: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var xyz: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + inline var stp: Bool3 + get() = Bool3(x, y, z) + set(value) { + x = value.x + y = value.y + z = value.z + } + + inline var rgba: Bool4 + get() = Bool4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + inline var xyzw: Bool4 + get() = Bool4(x, y, z, w) + set(value) { + x = value.x + y = value.y + z = value.z + w = value.w + } + inline var stpq: Bool4 + get() = Bool4(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): Bool2 { + return Bool2(get(index1), get(index2)) + } + operator fun get( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent): Bool3 { + return Bool3(get(index1), get(index2), get(index3)) + } + operator fun get( + index1: VectorComponent, + index2: VectorComponent, + index3: VectorComponent, + index4: VectorComponent): Bool4 { + return Bool4(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) = Bool2(get(index1), get(index2)) + operator fun get(index1: Int, index2: Int, index3: Int): Bool3 { + return Bool3(get(index1), get(index2), get(index3)) + } + operator fun get(index1: Int, index2: Int, index3: Int, index4: Int): Bool4 { + return Bool4(get(index1), get(index2), get(index3), get(index4)) + } + + inline operator fun invoke(index: Int) = get(index - 1) + + operator fun set(index: Int, v: Boolean) = 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: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index1: Int, index2: Int, index3: Int, index4: Int, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + set(index4, v) + } + + operator fun set(index: VectorComponent, v: Boolean) = 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: Boolean) { + set(index1, v) + set(index2, v) + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, + v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + } + + operator fun set(index1: VectorComponent, index2: VectorComponent, + index3: VectorComponent, index4: VectorComponent, v: Boolean) { + set(index1, v) + set(index2, v) + set(index3, v) + set(index4, v) + } +} diff --git a/android/gltfio-android/CMakeLists.txt b/android/gltfio-android/CMakeLists.txt index c7d0423de1..65f829ceae 100644 --- a/android/gltfio-android/CMakeLists.txt +++ b/android/gltfio-android/CMakeLists.txt @@ -21,10 +21,6 @@ add_library(filaflat STATIC IMPORTED) set_target_properties(filaflat PROPERTIES IMPORTED_LOCATION ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilaflat.a) -add_library(image STATIC IMPORTED) -set_target_properties(image PROPERTIES IMPORTED_LOCATION - ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimage.a) - add_library(ibl STATIC IMPORTED) set_target_properties(ibl PROPERTIES IMPORTED_LOCATION ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl.a) @@ -61,7 +57,6 @@ add_library(gltfio-jni-obj OBJECT src/main/cpp/Animator.cpp src/main/cpp/AssetLoader.cpp src/main/cpp/FilamentAsset.cpp - src/main/cpp/KtxLoader.cpp src/main/cpp/MaterialProvider.cpp src/main/cpp/ResourceLoader.cpp) @@ -91,7 +86,6 @@ if (NOT DISABLE_GLTFIO_JNI) filaflat filabridge geometry - image ibl utils log diff --git a/android/samples/sample-gltf-bloom/build.gradle b/android/samples/sample-gltf-bloom/build.gradle index b88833f6e1..1b464956a2 100644 --- a/android/samples/sample-gltf-bloom/build.gradle +++ b/android/samples/sample-gltf-bloom/build.gradle @@ -42,4 +42,5 @@ dependencies { implementation deps.kotlin implementation project(':filament-android') implementation project(':gltfio-android') + implementation project(':filament-utils-android') } diff --git a/android/samples/sample-gltf-bloom/src/main/java/com/google/android/filament/bloom/MainActivity.kt b/android/samples/sample-gltf-bloom/src/main/java/com/google/android/filament/bloom/MainActivity.kt index c8a7442faf..f26ac6d817 100644 --- a/android/samples/sample-gltf-bloom/src/main/java/com/google/android/filament/bloom/MainActivity.kt +++ b/android/samples/sample-gltf-bloom/src/main/java/com/google/android/filament/bloom/MainActivity.kt @@ -27,6 +27,7 @@ import android.view.animation.LinearInterpolator import com.google.android.filament.* import com.google.android.filament.android.UiHelper import com.google.android.filament.gltfio.* +import com.google.android.filament.utils.* import java.nio.ByteBuffer import java.nio.ByteOrder @@ -46,7 +47,7 @@ class MainActivity : Activity() { companion object { init { - Gltfio.init() + Utils.init() } } diff --git a/android/settings.gradle b/android/settings.gradle index c6d97a5cdd..f617285237 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -2,6 +2,7 @@ include ':filament-android' include ':filamat-android' include ':gltfio-android' +include ':filament-utils-android' // Samples include ':samples:sample-gltf-bloom' diff --git a/build.sh b/build.sh index 4d4e137e6e..428274a6dc 100755 --- a/build.sh +++ b/build.sh @@ -129,6 +129,8 @@ function build_clean { rm -Rf android/filamat-android/build android/filamat-android/.cxx rm -Rf android/gltfio-android/build android/gltfio-android/.externalNativeBuild rm -Rf android/gltfio-android/build android/gltfio-android/.cxx + rm -Rf android/filament-utils-android/build android/filament-utils-android/.externalNativeBuild + rm -Rf android/filament-utils-android/build android/filament-utils-android/.cxx } function build_desktop_target { @@ -373,7 +375,8 @@ function build_android { -Pfilament_dist_dir=../out/android-debug/filament \ -Pextra_cmake_args=${VULKAN_ANDROID_OPTION} \ :filament-android:assembleDebug \ - :gltfio-android:assembleDebug + :gltfio-android:assembleDebug \ + :filament-utils-android:assembleDebug ./gradlew \ -Pfilament_dist_dir=../out/android-debug/filament \ @@ -389,6 +392,9 @@ function build_android { echo "Installing out/gltfio-android-debug.aar..." cp gltfio-android/build/outputs/aar/gltfio-android-debug.aar ../out/ + + echo "Installing out/filament-utils-android-debug.aar..." + cp filament-utils-android/build/outputs/aar/filament-utils-android-debug.aar ../out/ fi fi @@ -396,7 +402,8 @@ function build_android { ./gradlew \ -Pfilament_dist_dir=../out/android-release/filament \ :filament-android:assembleRelease \ - :gltfio-android:assembleRelease + :gltfio-android:assembleRelease \ + :filament-utils-android:assembleRelease ./gradlew \ -Pfilament_dist_dir=../out/android-release/filament \ @@ -412,6 +419,9 @@ function build_android { echo "Installing out/gltfio-android-release.aar..." cp gltfio-android/build/outputs/aar/gltfio-android-release.aar ../out/ + + echo "Installing out/filament-utils-android-debug.aar..." + cp filament-utils-android/build/outputs/aar/filament-utils-android-release.aar ../out/ fi fi