render directly to Texture rather than PlatformView (Android only)

This commit is contained in:
Nick Fisher
2022-08-17 19:46:02 +10:00
parent 17f9d9fc01
commit 967be4d8a7
11 changed files with 773 additions and 773 deletions

View File

@@ -1,397 +0,0 @@
package app.polyvox.filament
import android.content.res.AssetManager
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.SurfaceTexture
import android.graphics.PixelFormat
import io.flutter.FlutterInjector
import android.os.CountDownTimer
import android.opengl.GLU
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import android.hardware.Camera
import android.opengl.GLSurfaceView
import android.view.SurfaceView
import android.view.TextureView
import android.view.View
import android.view.Surface
import android.widget.TextView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.DefaultLifecycleObserver
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
import java.io.IOException
import android.util.Log
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.Pointer
import com.sun.jna.ptr.PointerByReference
import com.sun.jna.ptr.IntByReference
import com.sun.jna.Structure
import com.sun.jna.NativeLibrary
import com.sun.jna.StringArray
import com.sun.jna.JNIEnv
import android.R.attr.path
import java.util.Collections;
import android.hardware.display.DisplayManager
import com.google.android.filament.android.*
import com.google.android.filament.*
import android.view.Choreographer
import android.view.SurfaceHolder
class FilamentView(
private val viewId: Int,
private val context: Context,
private val activity: Activity,
private val binaryMessenger: BinaryMessenger,
private val creationParams : Map<String?, Any?>?
) : DefaultLifecycleObserver,
MethodChannel.MethodCallHandler,
PlatformView {
companion object {
const val TAG = "FilamentView"
}
private val _view = SurfaceView(context)
override fun getView(): View {
return _view
}
private val _methodChannel: MethodChannel
private lateinit var _lib : FilamentInterop
private var _viewer : Pointer? = null
private lateinit var choreographer: Choreographer
private val frameScheduler = FrameCallback()
private lateinit var uiHelper : UiHelper
private lateinit var assetManager : AssetManager
init {
MethodChannel(binaryMessenger, PolyvoxFilamentPlugin.VIEW_TYPE + '_' + viewId).also {
_methodChannel = it
it.setMethodCallHandler(this)
}
_lib = Native.loadLibrary("filament_interop", FilamentInterop::class.java, Collections.singletonMap(Library.OPTION_ALLOW_OBJECTS, true))
_methodChannel.invokeMethod("ready", null)
choreographer = Choreographer.getInstance()
// _view.setAlpha(0)
_view.setZOrderOnTop(false)
_view.holder.setFormat(PixelFormat.TRANSPARENT)
_view.holder.addCallback (object : SurfaceHolder.Callback {
override fun surfaceChanged(holder:SurfaceHolder, format:Int, width:Int, height:Int) {
Log.v(TAG, "SURFACE CHANGED")
if(_viewer != null) {
_lib.update_viewport_and_camera_projection(_viewer!!, width, height, 1.0f);
}
}
override fun surfaceCreated(holder:SurfaceHolder) {
Log.v(TAG, "SURFACE CREATED")
if(_viewer == null) {
_viewer = _lib.filament_viewer_new(
_view.holder.surface as Object,
JNIEnv.CURRENT,
context.assets)
choreographer.postFrameCallback(frameScheduler)
activity.window.setFormat(PixelFormat.RGBA_8888)
uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK)
uiHelper.renderCallback = SurfaceCallback()
uiHelper.attachTo(_view)
}
_lib.destroy_swap_chain(_viewer!!)
_lib.create_swap_chain(_viewer!!, _view.holder.surface, JNIEnv.CURRENT)
}
override fun surfaceDestroyed(holder:SurfaceHolder) {
if(_viewer != null) {
_lib.destroy_swap_chain(_viewer!!)
}
}
})
}
override fun dispose() {
_methodChannel.setMethodCallHandler(null)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"reloadAssets" -> {
// context = context.createPackageContext(context.getPackageName(), 0)
// val assetManager = context.getAssets()
// val flutterJNI = FlutterJNI.Factory.provideFlutterJNI()
// flutterJNI.updateJavaAssetManager(assetManager, flutterApplicationInfo.flutterAssetsDir)
}
"setBackgroundImage" -> {
val args = call.arguments as String
val loader = FlutterInjector.instance().flutterLoader()
_lib.set_background_image(_viewer!!, loader.getLookupKeyForAsset(args))
Log.v(TAG, "Package name : ${context.getPackageName()}")
result.success("OK");
}
"loadSkybox" -> {
val args = call.arguments as String
val loader = FlutterInjector.instance().flutterLoader()
_lib.load_skybox(_viewer!!, loader.getLookupKeyForAsset(args))
result.success("OK");
}
"loadIbl" -> {
val args = call.arguments as String
val loader = FlutterInjector.instance().flutterLoader()
_lib.load_ibl(_viewer!!, loader.getLookupKeyForAsset(args))
result.success("OK");
}
"removeIbl" -> {
_lib.remove_ibl(_viewer!!)
result.success(true);
}
"removeSkybox" -> {
_lib.remove_skybox(_viewer!!)
result.success(true);
}
"loadGlb" -> {
if (_viewer == null)
return;
val loader = FlutterInjector.instance().flutterLoader()
val key = loader.getLookupKeyForAsset(call.arguments as String)
val key2 = loader.getLookupKeyForAsset(call.arguments as String, context.packageName)
val path = loader.findAppBundlePath()
val assetPtr = _lib.load_glb(
_viewer!!,
key
)
result.success(Pointer.nativeValue(assetPtr));
}
"loadGltf" -> {
if (_viewer == null)
return;
val args = call.arguments as ArrayList<Any?>
val loader = FlutterInjector.instance().flutterLoader()
val assetPtr = _lib.load_gltf(
_viewer!!,
loader.getLookupKeyForAsset(args[0] as String),
loader.getLookupKeyForAsset(args[1] as String)
)
result.success(Pointer.nativeValue(assetPtr));
}
"transformToUnitCube" -> {
val assetPtr = Pointer(call.arguments as Long);
_lib.transform_to_unit_cube(assetPtr)
result.success("OK");
}
"setPosition" -> {
val args = call.arguments as ArrayList<*>
val assetPtr = Pointer(args[0] as Long)
_lib.set_position(assetPtr, (args[1] as Double).toFloat(), (args[2] as Double).toFloat(), (args[3] as Double).toFloat())
result.success("OK");
}
"setRotation" -> {
val args = call.arguments as ArrayList<*>
val assetPtr = Pointer(args[0] as Long)
_lib.set_rotation(assetPtr, (args[1] as Double).toFloat(), (args[2] as Double).toFloat(), (args[3] as Double).toFloat(), (args[4] as Double).toFloat())
result.success("OK");
}
"setTexture" -> {
val args = call.arguments as ArrayList<*>
val loader = FlutterInjector.instance().flutterLoader()
val assetPtr = Pointer(args[0] as Long);
_lib.set_texture(assetPtr, loader.getLookupKeyForAsset(args[1] as String), args[2] as Int)
result.success("OK");
}
"setCamera" -> {
val args = call.arguments as ArrayList<*>
val success = _lib.set_camera(
_viewer!!,
Pointer(args[0] as Long),
args[1] as String,
)
if(success) {
result.success("OK");
} else {
result.error("failed","failed", "Failed to set camera")
}
}
"zoom" -> {
if(_viewer == null)
return;
_lib.scroll(_viewer!!, 0.0f, 0.0f, (call.arguments as Double).toFloat())
result.success("OK");
}
"getTargetNames" -> {
if(_viewer == null)
return;
val countPtr = IntByReference();
val args = call.arguments as ArrayList<*>
val namesPtr = _lib.get_target_names(Pointer(args[0] as Long), args[1] as String, countPtr)
val names = namesPtr.getStringArray(0, countPtr.value);
for(i in 0..countPtr.value-1) {
Log.v(TAG, "Got target names ${names[i]} ${names[i].length}")
}
val namesAsList = names.toCollection(ArrayList())
_lib.free_pointer(namesPtr, countPtr.getValue())
result.success(namesAsList)
}
"getAnimationNames" -> {
val assetPtr = Pointer(call.arguments as Long)
val countPtr = IntByReference();
val arrPtr = _lib.get_animation_names(assetPtr, countPtr)
val names = arrPtr.getStringArray(0, countPtr.value);
for(i in 0..countPtr.value-1) {
val name = names[i];
Log.v(TAG, "Got animation names ${name} ${name.length}")
}
_lib.free_pointer(arrPtr, 1)
result.success(names.toCollection(ArrayList()))
}
"applyWeights" -> {
val args = call.arguments as ArrayList<*>
val assetPtr = Pointer(args[0] as Long)
val weights = args[1] as ArrayList<Float>;
_lib.apply_weights(assetPtr, weights.toFloatArray(), weights.size)
result.success("OK");
}
"animateWeights" -> {
val args = call.arguments as ArrayList<Any?>
val assetPtr = Pointer(args[0] as Long)
val frames = args[1] as ArrayList<Float>;
val numWeights = args[2] as Int
val numFrames = args[3] as Int
val frameLenInMs = args[4] as Double
_lib.animate_weights(assetPtr, frames.toFloatArray(), numWeights, numFrames, frameLenInMs.toFloat())
result.success("OK");
}
"panStart" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_begin(_viewer!!, args[0] as Int, args[1] as Int, true)
result.success("OK");
}
"panUpdate" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_update(_viewer!!, args[0] as Int, args[1] as Int)
result.success("OK");
}
"panEnd" -> {
_lib.grab_end(_viewer!!)
result.success("OK");
}
"rotateStart" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_begin(_viewer!!, args[0] as Int, args[1] as Int, false)
result.success("OK");
}
"rotateUpdate" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_update(_viewer!!, args[0] as Int, args[1] as Int)
result.success("OK");
}
"rotateEnd" -> {
_lib.grab_end(_viewer!!)
result.success("OK");
}
"grabStart" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_begin(_viewer!!, args[0] as Int, args[1] as Int, true)
result.success("OK");
}
"grabUpdate" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_update(_viewer!!, args[0] as Int, args[1] as Int)
result.success("OK");
}
"grabEnd" -> {
_lib.grab_end(_viewer!!)
result.success("OK");
}
"removeAsset" -> {
_lib.remove_asset(_viewer!!, Pointer(call.arguments as Long))
result.success("OK");
}
"clearAssets" -> {
_lib.clear_assets(_viewer!!)
result.success("OK");
}
"playAnimation" -> {
val args = call.arguments as ArrayList<Any?>
_lib.play_animation(Pointer(args[0] as Long), args[1] as Int, args[2] as Boolean)
result.success("OK")
}
else -> {
result.notImplemented()
}
}
}
inner class SurfaceCallback : UiHelper.RendererCallback {
override fun onNativeWindowChanged(surface: Surface) {
_lib.destroy_swap_chain(_viewer!!)
_lib.create_swap_chain(_viewer!!, surface, JNIEnv.CURRENT)
}
override fun onDetachedFromSurface() {
_lib.destroy_swap_chain(_viewer!!)
}
override fun onResized(width: Int, height: Int) {
_lib.update_viewport_and_camera_projection(_viewer!!, width, height, 1.0f)
}
}
inner class FrameCallback : Choreographer.FrameCallback {
private val startTime = System.nanoTime()
override fun doFrame(frameTimeNanos: Long) {
choreographer.postFrameCallback(this)
_lib.render(_viewer!!)
}
}
}

View File

@@ -1,20 +0,0 @@
package app.polyvox.filament
import io.flutter.plugin.common.BinaryMessenger
import android.app.Activity
import android.content.Context
import android.view.View
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class FilamentViewFactory(
private val activity: Activity,
private val binaryMessenger: BinaryMessenger
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return FilamentView(viewId, context!!, activity, binaryMessenger, creationParams)
}
}

View File

@@ -7,6 +7,7 @@ import androidx.lifecycle.Lifecycle
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@@ -14,12 +15,82 @@ import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference
import android.content.res.AssetManager
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import io.flutter.FlutterInjector
import android.os.CountDownTimer
import android.opengl.GLU
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import android.hardware.Camera
import android.opengl.GLSurfaceView
import android.view.SurfaceView
import android.view.TextureView
import android.view.View
import android.view.Surface
import android.widget.TextView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.DefaultLifecycleObserver
import io.flutter.plugin.platform.PlatformView
import java.io.IOException
import android.util.Log
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.Pointer
import com.sun.jna.ptr.PointerByReference
import com.sun.jna.ptr.IntByReference
import com.sun.jna.Structure
import com.sun.jna.NativeLibrary
import com.sun.jna.StringArray
import com.sun.jna.JNIEnv
import android.R.attr.path
import android.graphics.*
import java.util.Collections;
import android.hardware.display.DisplayManager
import com.google.android.filament.android.*
import com.google.android.filament.*
import android.view.Choreographer
import android.view.Surface.CHANGE_FRAME_RATE_ALWAYS
import android.view.Surface.FRAME_RATE_COMPATIBILITY_DEFAULT
import android.view.SurfaceHolder
/** PolyvoxFilamentPlugin */
class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
inner class FrameCallback : Choreographer.FrameCallback {
private val startTime = System.nanoTime()
override fun doFrame(frameTimeNanos: Long) {
choreographer.postFrameCallback(this)
if(!surface.isValid()) {
Log.v(TAG, "INVALID")
}
_lib.render(_viewer!!)
}
}
companion object {
const val VIEW_TYPE = "app.polyvox.filament/filament_view"
const val CHANNEL_NAME = "app.polyvox.filament/event"
const val TAG = "FilamentPlugin"
}
/// The MethodChannel that will the communication between Flutter and native Android
@@ -28,41 +99,307 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
/// Keep a referene to the plugin binding so we can defer construction of a FilamentViewFactory
/// until Activity is attached.
/// Keep a referene to the plugin binding so we can use the TextureRegistry when initialize is called from the platform channel.
private lateinit var flutterPluginBinding : FlutterPlugin.FlutterPluginBinding
private var lifecycle: Lifecycle? = null
private lateinit var _lib : FilamentInterop
private var _viewer : Pointer? = null
private lateinit var choreographer: Choreographer
private val frameCallback = FrameCallback()
private lateinit var assetManager : AssetManager
private lateinit var surface: Surface
private lateinit var surfaceTexture: SurfaceTexture
private lateinit var activity:Activity
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
this.flutterPluginBinding = flutterPluginBinding
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "app.polyvox.filament")
channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
channel.setMethodCallHandler(this)
_lib = Native.loadLibrary("filament_interop", FilamentInterop::class.java, Collections.singletonMap(Library.OPTION_ALLOW_OBJECTS, true))
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
lifecycle = (binding.lifecycle as? HiddenLifecycleReference)?.lifecycle
flutterPluginBinding
.platformViewRegistry
.registerViewFactory(VIEW_TYPE, FilamentViewFactory(binding.activity, flutterPluginBinding.binaryMessenger))
activity = binding.activity
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"initialize" -> {
val args = call.arguments as ArrayList<Int>
val width = args[0]
val height = args[1]
val entry = flutterPluginBinding.textureRegistry.createSurfaceTexture();
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
choreographer = Choreographer.getInstance()
surfaceTexture = entry.surfaceTexture()
surfaceTexture.setDefaultBufferSize(width, height)
surface = Surface(surfaceTexture)
_viewer = _lib.filament_viewer_new(
surface as Object,
JNIEnv.CURRENT,
(activity as Context).assets)
choreographer.postFrameCallback(frameCallback)
activity.window.setFormat(PixelFormat.RGBA_8888)
_lib.update_viewport_and_camera_projection(_viewer!!, width, height, 1.0f);
result.success(entry.id().toInt())
}
"resize" -> {
val args = call.arguments as ArrayList<Int>
val width = args[0]
val height = args[1]
surfaceTexture.setDefaultBufferSize(width, height)
_lib.update_viewport_and_camera_projection(_viewer!!, width, height, 1.0f);
result.success(null)
}
"reloadAssets" -> {
// context = context.createPackageContext(context.getPackageName(), 0)
// val assetManager = context.getAssets()
// val flutterJNI = FlutterJNI.Factory.provideFlutterJNI()
// flutterJNI.updateJavaAssetManager(assetManager, flutterApplicationInfo.flutterAssetsDir)
}
"setBackgroundImage" -> {
val args = call.arguments as String
val loader = FlutterInjector.instance().flutterLoader()
_lib.set_background_image(_viewer!!, loader.getLookupKeyForAsset(args))
_lib.render(_viewer!!)
result.success("OK");
}
"loadSkybox" -> {
val args = call.arguments as String
val loader = FlutterInjector.instance().flutterLoader()
_lib.load_skybox(_viewer!!, loader.getLookupKeyForAsset(args))
result.success("OK");
}
"loadIbl" -> {
val args = call.arguments as String
val loader = FlutterInjector.instance().flutterLoader()
_lib.load_ibl(_viewer!!, loader.getLookupKeyForAsset(args))
result.success("OK");
}
"removeIbl" -> {
_lib.remove_ibl(_viewer!!)
result.success(true);
}
"removeSkybox" -> {
_lib.remove_skybox(_viewer!!)
result.success(true);
}
"loadGlb" -> {
if (_viewer == null)
return;
val loader = FlutterInjector.instance().flutterLoader()
val key = loader.getLookupKeyForAsset(call.arguments as String)
val key2 = loader.getLookupKeyForAsset(call.arguments as String, (activity as Context).packageName)
val path = loader.findAppBundlePath()
val assetPtr = _lib.load_glb(
_viewer!!,
key
)
result.success(Pointer.nativeValue(assetPtr));
}
"loadGltf" -> {
if (_viewer == null)
return;
val args = call.arguments as ArrayList<Any?>
val loader = FlutterInjector.instance().flutterLoader()
val assetPtr = _lib.load_gltf(
_viewer!!,
loader.getLookupKeyForAsset(args[0] as String),
loader.getLookupKeyForAsset(args[1] as String)
)
result.success(Pointer.nativeValue(assetPtr));
}
"transformToUnitCube" -> {
val assetPtr = Pointer(call.arguments as Long);
_lib.transform_to_unit_cube(assetPtr)
result.success("OK");
}
"setPosition" -> {
val args = call.arguments as ArrayList<*>
val assetPtr = Pointer(args[0] as Long)
_lib.set_position(assetPtr, (args[1] as Double).toFloat(), (args[2] as Double).toFloat(), (args[3] as Double).toFloat())
result.success("OK");
}
"setRotation" -> {
val args = call.arguments as ArrayList<*>
val assetPtr = Pointer(args[0] as Long)
_lib.set_rotation(assetPtr, (args[1] as Double).toFloat(), (args[2] as Double).toFloat(), (args[3] as Double).toFloat(), (args[4] as Double).toFloat())
result.success("OK");
}
"setTexture" -> {
val args = call.arguments as ArrayList<*>
val loader = FlutterInjector.instance().flutterLoader()
val assetPtr = Pointer(args[0] as Long);
_lib.set_texture(assetPtr, loader.getLookupKeyForAsset(args[1] as String), args[2] as Int)
result.success("OK");
}
"setCamera" -> {
val args = call.arguments as ArrayList<*>
val success = _lib.set_camera(
_viewer!!,
Pointer(args[0] as Long),
args[1] as String,
)
if(success) {
result.success("OK");
} else {
result.error("failed","failed", "Failed to set camera")
}
}
"zoom" -> {
if(_viewer == null)
return;
_lib.scroll(_viewer!!, 0.0f, 0.0f, (call.arguments as Double).toFloat())
result.success("OK");
}
"getTargetNames" -> {
if(_viewer == null)
return;
val countPtr = IntByReference();
val args = call.arguments as ArrayList<*>
val namesPtr = _lib.get_target_names(Pointer(args[0] as Long), args[1] as String, countPtr)
val names = namesPtr.getStringArray(0, countPtr.value);
for(i in 0..countPtr.value-1) {
Log.v(TAG, "Got target names ${names[i]} ${names[i].length}")
}
val namesAsList = names.toCollection(ArrayList())
_lib.free_pointer(namesPtr, countPtr.getValue())
result.success(namesAsList)
}
"getAnimationNames" -> {
val assetPtr = Pointer(call.arguments as Long)
val countPtr = IntByReference();
val arrPtr = _lib.get_animation_names(assetPtr, countPtr)
val names = arrPtr.getStringArray(0, countPtr.value);
for(i in 0..countPtr.value-1) {
val name = names[i];
Log.v(TAG, "Got animation names ${name} ${name.length}")
}
_lib.free_pointer(arrPtr, 1)
result.success(names.toCollection(ArrayList()))
}
"applyWeights" -> {
val args = call.arguments as ArrayList<*>
val assetPtr = Pointer(args[0] as Long)
val weights = args[1] as ArrayList<Float>;
_lib.apply_weights(assetPtr, weights.toFloatArray(), weights.size)
result.success("OK");
}
"animateWeights" -> {
val args = call.arguments as ArrayList<Any?>
val assetPtr = Pointer(args[0] as Long)
val frames = args[1] as ArrayList<Float>;
val numWeights = args[2] as Int
val numFrames = args[3] as Int
val frameLenInMs = args[4] as Double
_lib.animate_weights(assetPtr, frames.toFloatArray(), numWeights, numFrames, frameLenInMs.toFloat())
result.success("OK");
}
"panStart" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_begin(_viewer!!, args[0] as Int, args[1] as Int, true)
result.success("OK");
}
"panUpdate" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_update(_viewer!!, args[0] as Int, args[1] as Int)
result.success("OK");
}
"panEnd" -> {
_lib.grab_end(_viewer!!)
result.success("OK");
}
"rotateStart" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_begin(_viewer!!, args[0] as Int, args[1] as Int, false)
result.success("OK");
}
"rotateUpdate" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_update(_viewer!!, args[0] as Int, args[1] as Int)
result.success("OK");
}
"rotateEnd" -> {
_lib.grab_end(_viewer!!)
result.success("OK");
}
"grabStart" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_begin(_viewer!!, args[0] as Int, args[1] as Int, true)
result.success("OK");
}
"grabUpdate" -> {
val args = call.arguments as ArrayList<Any?>
_lib.grab_update(_viewer!!, args[0] as Int, args[1] as Int)
result.success("OK");
}
"grabEnd" -> {
_lib.grab_end(_viewer!!)
result.success("OK");
}
"removeAsset" -> {
_lib.remove_asset(_viewer!!, Pointer(call.arguments as Long))
result.success("OK");
}
"clearAssets" -> {
_lib.clear_assets(_viewer!!)
result.success("OK");
}
"playAnimation" -> {
val args = call.arguments as ArrayList<Any?>
_lib.play_animation(Pointer(args[0] as Long), args[1] as Int, args[2] as Boolean)
result.success("OK")
}
else -> {
result.notImplemented()
}
}
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
//_lib.destroy_swap_chain(_viewer!!)
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
//_lib.create_swap_chain(_viewer!!, surface, JNIEnv.CURRENT)
}
override fun onDetachedFromActivityForConfigChanges() {
@@ -70,6 +407,6 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
}
override fun onDetachedFromActivity() {
lifecycle = null
lifecycle = null
}
}

View File

@@ -56,10 +56,15 @@ android {
buildTypes {
release {
signingConfig signingConfigs.debug
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
packagingOptions {
}
aaptOptions {
noCompress "ktx"
@@ -71,13 +76,10 @@ flutter {
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.google.android.filament:filament-android:1.17.0"
implementation "com.google.android.filament:filament-utils-android:1.17.0"
implementation "com.google.android.filament:gltfio-android:1.17.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.core:core:1.7.0"
compile 'net.java.dev.jna:jna:5.10.0@aar'
}

View File

@@ -1,6 +1,8 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:polyvox_filament/filament_controller.dart';
import 'package:polyvox_filament/view/filament_widget.dart';
import 'package:polyvox_filament/filament_widget.dart';
void main() {
runApp(const MyApp());
@@ -23,6 +25,7 @@ class _MyAppState extends State<MyApp> {
List<String> _targets = [];
List<String> _animationNames = [];
bool _loop = false;
bool _vertical = false;
@override
void initState() {
@@ -32,240 +35,317 @@ class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
showPerformanceOverlay: true,
color: Colors.transparent,
home: Scaffold(
backgroundColor: Colors.transparent,
body: Column(children: [
Expanded(
child: SizedBox(
height: 200,
width: 200,
child: FilamentWidget(
controller: _filamentController,
))),
Align(
alignment: Alignment.bottomLeft,
child: Container(
color: Colors.white,
padding: const EdgeInsets.all(5),
child: PopupMenuButton<int>(
child: const Icon(Icons.menu),
onSelected: (int item) async {
switch (item) {
case 0:
await _filamentController.setBackgroundImage(
'assets/background.png');
break;
case 1:
await _filamentController.loadSkybox(
'assets/default_env/default_env_skybox.ktx');
await _filamentController.loadSkybox(
'assets/default_env/default_env_ibl.ktx');
break;
case 2:
await _filamentController.removeSkybox();
break;
case 3:
_cube = await _filamentController
.loadGlb('assets/cube.glb');
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
onScaleStart: (d) {
if (d.pointerCount == 1)
_filamentController.panStart(0.5, 0.5);
},
onScaleEnd: (d) {
// if(d.pointerCount == 2) {}
// _filamentController.panEnd(d. .focalPoint.dx,d.focalPoint.dy);
},
onScaleUpdate: (d) {
if (d.pointerCount == 1) {
// _filamentController.panUpdate(d.focalPoint.dx,d.focalPoint.dy);
} else {
_filamentController.zoom(10.0);
}
},
child: Container(
width: _vertical ? 100 : 200, height: _vertical ? 200 : 100,
alignment: Alignment.center,
child: SizedBox(
child: FilamentWidget(
controller: _filamentController,
)),
)),
Align(
alignment: Alignment.bottomLeft,
child: Container(
color: Colors.white,
margin: const EdgeInsets.all(15),
child: PopupMenuButton<int>(
padding: EdgeInsets.all(50),
iconSize: 36,
child: const Icon(Icons.menu),
onSelected: (int item) async {
switch (item) {
case 0:
await _filamentController
.setBackgroundImage(
'assets/background.png');
break;
case 1:
await _filamentController.loadSkybox(
'assets/default_env/default_env_skybox.ktx');
await _filamentController.loadSkybox(
'assets/default_env/default_env_ibl.ktx');
break;
case 2:
await _filamentController.removeSkybox();
break;
case 3:
_cube = await _filamentController
.loadGlb('assets/cube.glb');
_animationNames = await _filamentController
.getAnimationNames(_cube!);
break;
case 4:
if (_cube != null) {
await _filamentController.removeAsset(_cube!);
_animationNames = await _filamentController
.getAnimationNames(_cube!);
break;
case 4:
if (_cube != null) {
await _filamentController
.removeAsset(_cube!);
}
_cube = await _filamentController.loadGltf(
'assets/cube.gltf', 'assets');
print(await _filamentController
.getAnimationNames(_cube!));
break;
case 5:
if (_flightHelmet == null) {
_flightHelmet =
await _filamentController.loadGltf(
'assets/FlightHelmet/FlightHelmet.gltf',
'assets/FlightHelmet');
}
break;
case 6:
await _filamentController
.removeAsset(_cube!);
break;
case 7:
await _filamentController.applyWeights(
_cube!, List.filled(8, 1.0));
break;
case 8:
await _filamentController.applyWeights(
_cube!, List.filled(8, 0));
break;
case 9:
_filamentController.playAnimations(
_cube!,
List.generate(
_animationNames.length, (i) => i),
loop: _loop);
break;
case 10:
_filamentController.stopAnimation(_cube!);
break;
case 11:
setState(() {
_loop = !_loop;
});
break;
case 12:
_filamentController.zoom(-1.0);
break;
case 13:
_filamentController.zoom(1.0);
break;
case 14:
_filamentController.setCamera(
_cube!, "Camera_Orientation");
break;
case 15:
final framerate = 30;
final totalSecs = 5;
final numWeights = 8;
final totalFrames = framerate * totalSecs;
final frames = List.generate(
totalFrames,
(frame) => List.filled(
numWeights, frame / totalFrames));
_filamentController.animate(
_cube!,
frames.reduce((a, b) => a + b),
numWeights,
totalFrames,
1000 / framerate.toDouble());
break;
case 16:
final names = await _filamentController
.getTargetNames(_cube!, "Cube");
await showDialog(
builder: (ctx) {
return Container(
color: Colors.white,
height: 200,
width: 200,
child: Column(
mainAxisSize:
MainAxisSize.min,
children: names
.map((name) =>
Text(name))
.cast<Widget>()
.toList() +
<Widget>[
ElevatedButton(
onPressed: () =>
Navigator.of(
ctx)
.pop(),
child:
Text("Close"))
]));
},
context: context);
break;
case 17:
final names = await _filamentController
.getAnimationNames(_cube!);
await showDialog(
builder: (ctx) {
return Container(
color: Colors.white,
height: 200,
width: 200,
child: Column(
mainAxisSize:
MainAxisSize.min,
children: names
.map((name) =>
Text(name))
.cast<Widget>()
.toList() +
<Widget>[
ElevatedButton(
onPressed: () =>
Navigator.of(
ctx)
.pop(),
child:
Text("Close"))
]));
},
context: context);
break;
case 18:
await _filamentController.panStart(1, 1);
await _filamentController.panUpdate(1, 2);
await _filamentController.panEnd();
break;
case 19:
await _filamentController.panStart(1, 1);
await _filamentController.panUpdate(0, 0);
await _filamentController.panEnd();
break;
case 20:
await _filamentController.clearAssets();
break;
case 21:
await _filamentController.setTexture(
_cube!, "assets/background.png");
break;
case 22:
await _filamentController
.transformToUnitCube(_cube!);
break;
case 23:
await _filamentController.setPosition(
_cube!, 1.0, 1.0, -1.0);
break;
case 24:
await _filamentController.setRotation(
_cube!, pi / 2, 0.0, 1.0, 0.0);
break;
case 25:
setState(() {
_vertical = !_vertical;
});
}
_cube = await _filamentController.loadGltf(
'assets/cube.gltf', 'assets');
print(await _filamentController
.getAnimationNames(_cube!));
break;
case 5:
if (_flightHelmet == null) {
_flightHelmet =
await _filamentController.loadGltf(
'assets/FlightHelmet/FlightHelmet.gltf',
'assets/FlightHelmet');
}
break;
case 6:
await _filamentController.removeAsset(_cube!);
break;
case 7:
await _filamentController.applyWeights(
_cube!, List.filled(8, 1.0));
break;
case 8:
await _filamentController.applyWeights(
_cube!, List.filled(8, 0));
break;
case 9:
_filamentController.playAnimations(
_cube!,
List.generate(
_animationNames.length, (i) => i),
loop: _loop);
break;
case 10:
_filamentController.stopAnimation(_cube!);
break;
case 11:
setState(() {
_loop = !_loop;
});
break;
case 12:
_filamentController.zoom(-1.0);
break;
case 13:
_filamentController.zoom(1.0);
break;
case 14:
_filamentController.setCamera(
_cube!, "Camera_Orientation");
break;
case 15:
final framerate = 30;
final totalSecs = 5;
final numWeights = 8;
final totalFrames = framerate * totalSecs;
final frames = List.generate(
totalFrames,
(frame) => List.filled(
numWeights, frame / totalFrames));
_filamentController.animate(
_cube!,
frames.reduce((a, b) => a + b),
numWeights,
totalFrames,
1000 / framerate.toDouble());
break;
case 16:
final names = await _filamentController
.getTargetNames(_cube!, "Cube");
await showDialog(
builder: (ctx) {
return Container(
color: Colors.white,
height: 200,
width: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
children: names
.map((name) => Text(name))
.cast<Widget>()
.toList() +
<Widget>[
ElevatedButton(
onPressed: () =>
Navigator.of(ctx)
.pop(),
child: Text("Close"))
]));
},
context: context);
break;
case 17:
final names = await _filamentController
.getAnimationNames(_cube!);
await showDialog(
builder: (ctx) {
return Container(
color: Colors.white,
height: 200,
width: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
children: names
.map((name) => Text(name))
.cast<Widget>()
.toList() +
<Widget>[
ElevatedButton(
onPressed: () =>
Navigator.of(ctx)
.pop(),
child: Text("Close"))
]));
},
context: context);
break;
case 18:
await _filamentController.panStart(1, 1);
await _filamentController.panUpdate(1, 2);
await _filamentController.panEnd();
break;
case 19:
await _filamentController.panStart(1, 1);
await _filamentController.panUpdate(0, 0);
await _filamentController.panEnd();
break;
case 20:
await _filamentController.clearAssets();
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<int>>[
const PopupMenuItem(
value: 0,
child: Text("load background image")),
const PopupMenuItem(
value: 1,
child: Text('load skybox'),
),
const PopupMenuItem(
value: 2,
child: Text('remove skybox'),
),
const PopupMenuItem(
value: 3, child: Text('load cube GLB')),
const PopupMenuItem(
value: 4, child: Text('load cube GLTF')),
const PopupMenuItem(
value: 5,
child: Text('load flight helmet')),
const PopupMenuItem(
value: 6, child: Text('remove cube')),
const PopupMenuItem(
value: 20, child: Text('remove all assets')),
const PopupMenuItem(
value: 7,
child: Text('set all weights to 1')),
const PopupMenuItem(
value: 8,
child: Text('set all weights to 0')),
const PopupMenuItem(
value: 9,
child: Text('play all animations')),
const PopupMenuItem(
value: 10, child: Text('stop animations')),
PopupMenuItem(
value: 11,
child: Text(
"toggle animation loop (currently $_loop!)")),
const PopupMenuItem(
value: 12, child: Text('zoom in')),
const PopupMenuItem(
value: 13, child: Text('zoom out')),
const PopupMenuItem(
value: 14, child: Text('set camera')),
const PopupMenuItem(
value: 15, child: Text('animate weights')),
const PopupMenuItem(
value: 16, child: Text('get target names')),
const PopupMenuItem(
value: 17,
child: Text('get animation names')),
const PopupMenuItem(
value: 18, child: Text('pan left')),
const PopupMenuItem(
value: 19, child: Text('pan right')),
])))
])));
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<int>>[
const PopupMenuItem(
value: 0,
child: Text("load background image")),
const PopupMenuItem(
value: 1,
child: Text('load skybox'),
),
const PopupMenuItem(
value: 2,
child: Text('remove skybox'),
),
const PopupMenuItem(
value: 3, child: Text('load cube GLB')),
const PopupMenuItem(
value: 4,
child: Text('load cube GLTF')),
const PopupMenuItem(
value: 21,
child: Text('swap cube texture')),
const PopupMenuItem(
value: 22,
child: Text('transform to unit cube')),
const PopupMenuItem(
value: 23,
child:
Text('set position to 1, 1, -1')),
const PopupMenuItem(
value: 24,
child:
Text('rotate by pi around Y axis')),
const PopupMenuItem(
value: 5,
child: Text('load flight helmet')),
const PopupMenuItem(
value: 6, child: Text('remove cube')),
const PopupMenuItem(
value: 20,
child: Text('remove all assets')),
const PopupMenuItem(
value: 7,
child: Text('set all weights to 1')),
const PopupMenuItem(
value: 8,
child: Text('set all weights to 0')),
const PopupMenuItem(
value: 9,
child: Text('play all animations')),
const PopupMenuItem(
value: 10,
child: Text('stop animations')),
PopupMenuItem(
value: 11,
child: Text(
_loop ? "don't loop animation" : "loop animation")),
const PopupMenuItem(
value: 12, child: Text('zoom in')),
const PopupMenuItem(
value: 13, child: Text('zoom out')),
const PopupMenuItem(
value: 14, child: Text('set camera')),
const PopupMenuItem(
value: 15,
child: Text('animate weights')),
const PopupMenuItem(
value: 16,
child: Text('get target names')),
const PopupMenuItem(
value: 17,
child: Text('get animation names')),
const PopupMenuItem(
value: 18, child: Text('pan left')),
const PopupMenuItem(
value: 19, child: Text('pan right')),
PopupMenuItem(
value: 25, child: Text(_vertical ? 'set horizontal' : 'set vertical')),
])))
])));
}
}

View File

@@ -5,7 +5,10 @@ import 'package:flutter/services.dart';
typedef FilamentAsset = int;
abstract class FilamentController {
void onFilamentViewCreated(int id);
late int textureId;
Future initialize(int width, int height);
Future resize(int width, int height);
Future setBackgroundImage(String path);
Future loadSkybox(String skyboxPath);
Future removeSkybox();
@@ -45,28 +48,24 @@ abstract class FilamentController {
}
class PolyvoxFilamentController extends FilamentController {
late int _id;
late MethodChannel _channel;
final Function(int id)? onFilamentViewCreatedHandler;
late MethodChannel _channel = MethodChannel("app.polyvox.filament/event");
PolyvoxFilamentController({this.onFilamentViewCreatedHandler});
@override
void onFilamentViewCreated(int id) async {
_id = id;
_channel = MethodChannel("app.polyvox.filament/filament_view_$id");
PolyvoxFilamentController() {
_channel.setMethodCallHandler((call) async {
print("Received Filament method channel call : ${call.method}");
if (call.method == "ready") {
onFilamentViewCreatedHandler?.call(_id);
return Future.value(true);
} else {
throw Exception("Unknown method channel invocation ${call.method}");
}
throw Exception("Unknown method channel invocation ${call.method}");
});
}
Future initialize(int width, int height) async {
textureId = await _channel.invokeMethod("initialize", [width, height]);
}
Future resize(int width, int height) async {
await _channel.invokeMethod("resize", [width, height]);
}
@override
Future setBackgroundImage(String path) async {
await _channel.invokeMethod("setBackgroundImage", path);

88
lib/filament_widget.dart Normal file
View File

@@ -0,0 +1,88 @@
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'filament_controller.dart';
typedef ResizeCallback = void Function(Size oldSize, Size newSize);
class ResizeObserver extends SingleChildRenderObjectWidget {
final ResizeCallback onResized;
const ResizeObserver({
Key? key,
required this.onResized,
Widget? child,
}) : super(
key: key,
child: child,
);
@override
RenderObject createRenderObject(BuildContext context) =>
_RenderResizeObserver(onLayoutChangedCallback: onResized);
}
class _RenderResizeObserver extends RenderProxyBox {
final ResizeCallback onLayoutChangedCallback;
_RenderResizeObserver({
RenderBox? child,
required this.onLayoutChangedCallback,
}) : super(child);
late var _oldSize = size;
@override
void performLayout() {
super.performLayout();
if (size != _oldSize) {
onLayoutChangedCallback(_oldSize, size);
_oldSize = size;
}
}
}
class FilamentWidget extends StatefulWidget {
final FilamentController controller;
const FilamentWidget({Key? key, required this.controller}) : super(key: key);
@override
_FilamentWidgetState createState() => _FilamentWidgetState();
}
class _FilamentWidgetState extends State<FilamentWidget> {
bool _ready = false;
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
var size = ((context.findRenderObject()) as RenderBox).size;
print("Requesting texture creation for Filament of size $size");
await widget.controller.initialize(size.width.toInt(), size.height.toInt());
print("Filament texture available");
setState(() {
_ready = true;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
if(!_ready) {
return Container();
}
return ResizeObserver(
onResized: (Size oldSize, Size newSize) async {
await widget.controller.resize(newSize.width.toInt(), newSize.height.toInt());
},
child:Texture(
textureId: widget.controller.textureId,
filterQuality: FilterQuality.none,
));
}
}

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'filament_controller.dart';
import 'view/filament_widget.dart';
import 'filament_widget.dart';
class GestureDetectingFilamentView extends StatefulWidget {
final FilamentController controller;

View File

@@ -1,39 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'filament_view_platform.dart';
class FilamentView extends FilamentViewPlatform {
static const FILAMENT_VIEW_ID = 'app.polyvox.filament/filament_view';
@override
Widget buildView(
int creationId,
FilamentViewCreatedCallback onFilamentViewCreated,
) {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return AndroidView(
viewType: FILAMENT_VIEW_ID,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
onPlatformViewCreated: (id) {
onFilamentViewCreated(id);
});
case TargetPlatform.iOS:
return UiKitView(
viewType: FILAMENT_VIEW_ID,
onPlatformViewCreated: (int id) {
onFilamentViewCreated(id);
},
);
case TargetPlatform.windows:
return Text("Flutter doesn't support platform view on Windows yet.");
default:
return Text(
'$defaultTargetPlatform is not yet implemented by Filament plugin.');
}
}
}

View File

@@ -1,21 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:polyvox_filament/view/filament_view.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
typedef FilamentViewCreatedCallback = void Function(int id);
abstract class FilamentViewPlatform extends PlatformInterface {
FilamentViewPlatform() : super(token: _token);
static final Object _token = Object();
static final FilamentViewPlatform _instance = FilamentView();
static FilamentViewPlatform get instance => _instance;
Widget buildView(
int creationId,
FilamentViewCreatedCallback onFilamentViewCreated,
) {
throw UnimplementedError('buildView() has not been implemented.');
}
}

View File

@@ -1,29 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:polyvox_filament/view/filament_view.dart';
import '../filament_controller.dart';
import 'filament_view_platform.dart';
int _nextFilamentCreationId = 0;
class FilamentWidget extends StatefulWidget {
final FilamentController controller;
const FilamentWidget({Key? key, required this.controller}) : super(key: key);
@override
_FilamentWidgetState createState() => _FilamentWidgetState();
}
class _FilamentWidgetState extends State<FilamentWidget> {
final _viewId = _nextFilamentCreationId++;
@override
Widget build(BuildContext context) {
return FilamentViewPlatform.instance
.buildView(_viewId, widget.controller.onFilamentViewCreated);
}
}