diff --git a/android/common/CallbackUtils.h b/android/common/CallbackUtils.h index 0fa9c793bb81..6a631efe5c10 100644 --- a/android/common/CallbackUtils.h +++ b/android/common/CallbackUtils.h @@ -78,6 +78,9 @@ struct JniCallback { static void invoke(void* user); + jobject getCallbackObject() { return mCallback; } + JNIEnv* getJniEnv() { return mEnv; } + private: JniCallback(JNIEnv* env, jobject handler, jobject runnable); JniCallback(JniCallback const &) = delete; diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index 8e8caabbafd4..f3b1e4b7852e 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -20,6 +20,8 @@ #include #include +#include "common/CallbackUtils.h" + using namespace filament; extern "C" JNIEXPORT void JNICALL @@ -382,3 +384,41 @@ Java_com_google_android_filament_View_nIsScreenSpaceRefractionEnabled(JNIEnv *, View* view = (View*) nativeView; return (jboolean)view->isScreenSpaceRefractionEnabled(); } + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_View_nPick(JNIEnv* env, jclass clazz, + jlong nativeView, + jint x, jint y, jobject handler, jobject internalCallback) { + + // jniState will be initialized the first time this method is called + static const struct JniState { + jclass internalOnPickCallbackClass; + jfieldID renderableFieldId; + jfieldID depthFieldId; + jfieldID fragCoordXFieldId; + jfieldID fragCoordYFieldId; + jfieldID fragCoordZFieldId; + explicit JniState(JNIEnv* env) noexcept { + internalOnPickCallbackClass = env->FindClass("com/google/android/filament/View$InternalOnPickCallback"); + renderableFieldId = env->GetFieldID(internalOnPickCallbackClass, "mRenderable", "I"); + depthFieldId = env->GetFieldID(internalOnPickCallbackClass, "mDepth", "F"); + fragCoordXFieldId = env->GetFieldID(internalOnPickCallbackClass, "mFragCoordsX", "F"); + fragCoordYFieldId = env->GetFieldID(internalOnPickCallbackClass, "mFragCoordsY", "F"); + fragCoordZFieldId = env->GetFieldID(internalOnPickCallbackClass, "mFragCoordsZ", "F"); + } + } jniState(env); + + View* view = (View*) nativeView; + JniCallback *callback = JniCallback::make(env, handler, internalCallback); + view->pick(x, y, [callback](View::PickingQueryResult const& result) { + jobject obj = callback->getCallbackObject(); + JNIEnv* const env = callback->getJniEnv(); + env->SetIntField(obj, jniState.renderableFieldId, (jint)result.renderable.getId()); + env->SetFloatField(obj, jniState.depthFieldId, result.depth); + env->SetFloatField(obj, jniState.fragCoordXFieldId, result.fragCoords.x); + env->SetFloatField(obj, jniState.fragCoordYFieldId, result.fragCoords.y); + env->SetFloatField(obj, jniState.fragCoordZFieldId, result.fragCoords.z); + JniCallback::invoke(callback); // this destroys JniCallback + }); +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java index 682482e1601c..ef32575aacf9 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/View.java +++ b/android/filament-android/src/main/java/com/google/android/filament/View.java @@ -1548,6 +1548,71 @@ public DepthOfFieldOptions getDepthOfFieldOptions() { return mDepthOfFieldOptions; } + /** + * A class containing the result of a picking query + */ + public static class PickingQueryResult { + /** The entity of the renderable at the picking query location */ + @Entity public int renderable; + /** The value of the depth buffer at the picking query location */ + public float depth; + /** The fragment coordinate in GL convention at the the picking query location */ + @NonNull public float[] fragCoords = new float[3]; + }; + + /** + * An interface to implement a custom class to receive results of picking queries. + */ + public interface OnPickCallback { + /** + * onPick() is called by the specified Handler in {@link View#pick} when the picking query + * result is available. + * @param result An instance of {@link PickingQueryResult}. + */ + void onPick(@NonNull PickingQueryResult result); + } + + /** + * Creates a picking query. Multiple queries can be created (e.g.: multi-touch). + * Picking queries are all executed when {@link Renderer#render} is called on this View. + * The provided callback is guaranteed to be called at some point in the future. + * + * Typically it takes a couple frames to receive the result of a picking query. + * + * @param x Horizontal coordinate to query in the viewport with origin on the left. + * @param y Vertical coordinate to query on the viewport with origin at the bottom. + * @param handler An {@link java.util.concurrent.Executor Executor}. + * On Android this can also be a {@link android.os.Handler Handler}. + * @param callback User callback executed by handler when the picking query + * result is available. + */ + public void pick(int x, int y, + @Nullable Object handler, @Nullable OnPickCallback callback) { + InternalOnPickCallback internalCallback = new InternalOnPickCallback(callback); + nPick(getNativeObject(), x, y, handler, internalCallback); + } + + private static class InternalOnPickCallback implements Runnable { + public InternalOnPickCallback(OnPickCallback mUserCallback) { + this.mUserCallback = mUserCallback; + } + @Override + public void run() { + mPickingQueryResult.renderable = mRenderable; + mPickingQueryResult.depth = mDepth; + mPickingQueryResult.fragCoords[0] = mFragCoordsX; + mPickingQueryResult.fragCoords[1] = mFragCoordsY; + mPickingQueryResult.fragCoords[2] = mFragCoordsZ; + mUserCallback.onPick(mPickingQueryResult); + } + private final OnPickCallback mUserCallback; + private final PickingQueryResult mPickingQueryResult = new PickingQueryResult(); + @Entity int mRenderable; + float mDepth; + float mFragCoordsX; + float mFragCoordsY; + float mFragCoordsZ; + } public long getNativeObject() { if (mNativeObject == 0) { @@ -1598,4 +1663,5 @@ private static native void nSetDepthOfFieldOptions(long nativeView, float cocSca private static native boolean nIsShadowingEnabled(long nativeView); private static native void nSetScreenSpaceRefractionEnabled(long nativeView, boolean enabled); private static native boolean nIsScreenSpaceRefractionEnabled(long nativeView); + private static native void nPick(long nativeView, int x, int y, Object handler, InternalOnPickCallback internalCallback); }