From 5f94a12d650041993a166c7fe223d9c6fef62bc4 Mon Sep 17 00:00:00 2001 From: Sebastian Trattnig Date: Wed, 29 Jun 2022 11:58:49 +0200 Subject: [PATCH 1/2] Added android pinch gesture added scaling (pinch gesture) to only android smartphones --- .../lars/ar_flutter_plugin/AndroidARView.kt | 62 +++++++++++++++---- .../lars/ar_flutter_plugin/ArModelBuilder.kt | 49 +++++++++++++-- lib/managers/ar_object_manager.dart | 30 +++++++++ lib/managers/ar_session_manager.dart | 2 + 4 files changed, 125 insertions(+), 18 deletions(-) diff --git a/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/AndroidARView.kt b/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/AndroidARView.kt index 1b39d2ea..d627c256 100644 --- a/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/AndroidARView.kt +++ b/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/AndroidARView.kt @@ -80,6 +80,7 @@ internal class AndroidARView( // Setting defaults private var enableRotation = false private var enablePans = false + private var enableScaling = false private var keepNodeSelected = true; private var footprintSelectionVisualizer = FootprintSelectionVisualizer() // Model builder @@ -302,7 +303,7 @@ internal class AndroidARView( MaterialFactory.makeTransparentWithColor(context, Color(255f, 255f, 255f, 0.3f)) .thenAccept { mat -> - footprintSelectionVisualizer.footprintRenderable = ShapeFactory.makeCylinder(0.7f,0.05f, Vector3(0f,0f,0f), mat) + footprintSelectionVisualizer.footprintRenderable = ShapeFactory.makeCylinder(0.7f,0.05f, Vector3(0f,0f,0.25f), mat) } transformationSystem = @@ -453,6 +454,7 @@ internal class AndroidARView( val argShowWorldOrigin: Boolean? = call.argument("showWorldOrigin") val argHandleTaps: Boolean? = call.argument("handleTaps") val argHandleRotation: Boolean? = call.argument("handleRotation") + val argHandleScaling: Boolean? = call.argument("handleScaling") val argHandlePans: Boolean? = call.argument("handlePans") val argShowAnimatedGuide: Boolean? = call.argument("showAnimatedGuide") @@ -463,8 +465,9 @@ internal class AndroidARView( onNodeTapListener = com.google.ar.sceneform.Scene.OnPeekTouchListener { hitTestResult, motionEvent -> //if (hitTestResult.node != null){ //transformationSystem.selectionVisualizer.applySelectionVisual(hitTestResult.node as TransformableNode) - //transformationSystem.selectNode(hitTestResult.node as TransformableNode) + //transformationSystem.selectNode(hitTestResult.node as TransformableNode) //} + if (hitTestResult.node != null && motionEvent?.action == MotionEvent.ACTION_DOWN) { objectManagerChannel.invokeMethod("onNodeTap", listOf(hitTestResult.node?.name)) } @@ -580,6 +583,12 @@ internal class AndroidARView( } else { enablePans = false } + if (argHandleScaling == + true) { // explicit comparison necessary because of nullable type + enableScaling = true + } else { + enableScaling = false + } result.success(null) } @@ -636,7 +645,7 @@ internal class AndroidARView( transformationSystem.selectNode(null) keepNodeSelected = true } - if (!enablePans && !enableRotation){ + if (!enablePans && !enableRotation && !enableScaling){ //unselect all nodes as we do not want the selection visualizer transformationSystem.selectNode(null) } @@ -645,28 +654,55 @@ internal class AndroidARView( private fun addNode(dict_node: HashMap, dict_anchor: HashMap? = null): CompletableFuture{ val completableFutureSuccess: CompletableFuture = CompletableFuture() - + try { when (dict_node["type"] as Int) { 0 -> { // GLTF2 Model from Flutter asset folder // Get path to given Flutter asset val loader: FlutterLoader = FlutterInjector.instance().flutterLoader() val key: String = loader.getLookupKeyForAsset(dict_node["uri"] as String) - + println("FREDTAP2: added model: ") // Add object to scene - modelBuilder.makeNodeFromGltf(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, dict_node["name"] as String, key, dict_node["transformation"] as ArrayList) + modelBuilder.makeNodeFromGltf(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling, dict_node["name"] as String, key, dict_node["transformation"] as ArrayList) .thenAccept{node -> val anchorName: String? = dict_anchor?.get("name") as? String val anchorType: Int? = dict_anchor?.get("type") as? Int + if (anchorName != null && anchorType != null) { val anchorNode = arSceneView.scene.findByName(anchorName) as AnchorNode? if (anchorNode != null) { + val mainHandler = Handler(viewContext.mainLooper) + val runnable = Runnable {sessionManagerChannel.invokeMethod("onError", listOf("1 LOADED RENDERABLE " + node.getRenderable()?.getMaterial().toString() )) } + mainHandler.post(runnable) + + println("3 LOADED RENDERABLE " + node.getRenderable()?.getMaterial().toString()) + for(i in 1..10){ + println(i) + println(node.getRenderable()?.getMaterial().toString()) + + // node.getRenderable()?.setMaterial(i,node.getRenderable()?.getMaterial(i).setFloat4("baseColorFactor", 1,1,1,1)) + } + /*for(int x = 0; x < node.getRenderable()?.getSubmeshCount(); x++) { + Material m; + m = node.getRenderable()?.getMaterial(x).makeCopy(); + m.setFloat4("baseColorFactor", 1,1,1,1); // changing the color factor works, setFloat("alphaCutoff", cutoffValue); does not seem to work + node.getRenderable()?.setMaterial(x,m); + }*/ + // node.getRenderable()?.getMaterial().setFloat4("baseColorFactor", 1,1,1,1) + println("3a LOADED RENDERABLE " + node.getRenderable()?.getMaterial().toString()) anchorNode.addChild(node) + completableFutureSuccess.complete(true) + println("4 LOADED RENDERABLE " + node.getRenderable()?.getSubmeshCount().toString()) + println("5 LOADED RENDERABLE " + node.getRenderable()?.getSubmeshCount().toString()) } else { completableFutureSuccess.complete(false) } } else { arSceneView.scene.addChild(node) + val mainHandler = Handler(viewContext.mainLooper) + val runnable = Runnable {sessionManagerChannel.invokeMethod("onError", listOf("2 LOADED RENDERABLE " + node )) } + mainHandler.post(runnable) + completableFutureSuccess.complete(true) } completableFutureSuccess.complete(false) @@ -674,14 +710,15 @@ internal class AndroidARView( .exceptionally { throwable -> // Pass error to session manager (this has to be done on the main thread if this activity) val mainHandler = Handler(viewContext.mainLooper) - val runnable = Runnable {sessionManagerChannel.invokeMethod("onError", listOf("Unable to load renderable" + dict_node["uri"] as String)) } + val runnable = Runnable {sessionManagerChannel.invokeMethod("onError", listOf("Unable to load renderable " + dict_node["uri"] as String)) } mainHandler.post(runnable) completableFutureSuccess.completeExceptionally(throwable) null // return null because java expects void return (in java, void has no instance, whereas in Kotlin, this closure returns a Unit which has one instance) } + } 1 -> { // GLB Model from the web - modelBuilder.makeNodeFromGlb(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, dict_node["name"] as String, dict_node["uri"] as String, dict_node["transformation"] as ArrayList) + modelBuilder.makeNodeFromGlb(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling, dict_node["name"] as String, dict_node["uri"] as String, dict_node["transformation"] as ArrayList) .thenAccept{node -> val anchorName: String? = dict_anchor?.get("name") as? String val anchorType: Int? = dict_anchor?.get("type") as? Int @@ -711,7 +748,7 @@ internal class AndroidARView( val documentsPath = viewContext.getApplicationInfo().dataDir val assetPath = documentsPath + "/app_flutter/" + dict_node["uri"] as String - modelBuilder.makeNodeFromGlb(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, dict_node["name"] as String, assetPath as String, dict_node["transformation"] as ArrayList) // + modelBuilder.makeNodeFromGlb(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling, dict_node["name"] as String, assetPath as String, dict_node["transformation"] as ArrayList) // .thenAccept{node -> val anchorName: String? = dict_anchor?.get("name") as? String val anchorType: Int? = dict_anchor?.get("type") as? Int @@ -743,7 +780,7 @@ internal class AndroidARView( val assetPath = documentsPath + "/app_flutter/" + dict_node["uri"] as String // Add object to scene - modelBuilder.makeNodeFromGltf(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, dict_node["name"] as String, assetPath, dict_node["transformation"] as ArrayList) + modelBuilder.makeNodeFromGltf(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling, dict_node["name"] as String, assetPath, dict_node["transformation"] as ArrayList) .thenAccept{node -> val anchorName: String? = dict_anchor?.get("name") as? String val anchorType: Int? = dict_anchor?.get("type") as? Int @@ -776,7 +813,8 @@ internal class AndroidARView( } catch (e: java.lang.Exception) { completableFutureSuccess.completeExceptionally(e) } - + println("FREDTAP: added model: ") + // println("FREDTAP: added model: " + tmp.toString()) return completableFutureSuccess } @@ -800,7 +838,7 @@ internal class AndroidARView( return true } if (motionEvent != null && motionEvent.action == MotionEvent.ACTION_DOWN) { - if (transformationSystem.selectedNode == null || (!enablePans && !enableRotation)){ + if (transformationSystem.selectedNode == null || (!enablePans && !enableRotation && !enableScaling)){ val allHitResults = frame?.hitTest(motionEvent) ?: listOf() val planeAndPointHitResults = allHitResults.filter { ((it.trackable is Plane) || (it.trackable is Point)) } diff --git a/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/ArModelBuilder.kt b/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/ArModelBuilder.kt index 455e808f..6eb26ff9 100644 --- a/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/ArModelBuilder.kt +++ b/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/ArModelBuilder.kt @@ -91,10 +91,10 @@ class ArModelBuilder { } // Creates a node form a given gltf model path or URL. The gltf asset loading in Scenform is asynchronous, so the function returns a completable future of type Node - fun makeNodeFromGltf(context: Context, transformationSystem: TransformationSystem, objectManagerChannel: MethodChannel, enablePans: Boolean, enableRotation: Boolean, name: String, modelPath: String, transformation: ArrayList): CompletableFuture { + fun makeNodeFromGltf(context: Context, transformationSystem: TransformationSystem, objectManagerChannel: MethodChannel, enablePans: Boolean, enableRotation: Boolean, enableScaling: Boolean, name: String, modelPath: String, transformation: ArrayList): CompletableFuture { val completableFutureNode: CompletableFuture = CompletableFuture() - - val gltfNode = CustomTransformableNode(transformationSystem, objectManagerChannel, enablePans, enableRotation) + println("MAKENODEFROMGLTF: ") + val gltfNode = CustomTransformableNode(transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling) ModelRenderable.builder() .setSource(context, RenderableSource.builder().setSource( @@ -118,14 +118,15 @@ class ArModelBuilder { null // return null because java expects void return (in java, void has no instance, whereas in Kotlin, this closure returns a Unit which has one instance) } + return completableFutureNode } // Creates a node form a given glb model path or URL. The gltf asset loading in Sceneform is asynchronous, so the function returns a compleatable future of type Node - fun makeNodeFromGlb(context: Context, transformationSystem: TransformationSystem, objectManagerChannel: MethodChannel, enablePans: Boolean, enableRotation: Boolean, name: String, modelPath: String, transformation: ArrayList): CompletableFuture { + fun makeNodeFromGlb(context: Context, transformationSystem: TransformationSystem, objectManagerChannel: MethodChannel, enablePans: Boolean, enableRotation: Boolean, enableScaling: Boolean, name: String, modelPath: String, transformation: ArrayList): CompletableFuture { val completableFutureNode: CompletableFuture = CompletableFuture() - val gltfNode = CustomTransformableNode(transformationSystem, objectManagerChannel, enablePans, enableRotation) + val gltfNode = CustomTransformableNode(transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling) //gltfNode.scaleController.isEnabled = false //gltfNode.translationController.isEnabled = false @@ -164,12 +165,14 @@ class ArModelBuilder { } } -class CustomTransformableNode(transformationSystem: TransformationSystem, objectManagerChannel: MethodChannel, enablePans: Boolean, enableRotation: Boolean) : +class CustomTransformableNode(transformationSystem: TransformationSystem, objectManagerChannel: MethodChannel, enablePans: Boolean, enableRotation: Boolean, enableScaling: Boolean) : TransformableNode(transformationSystem) { // private lateinit var customTranslationController: CustomTranslationController private lateinit var customRotationController: CustomRotationController + + private lateinit var customScaleController: CustomScaleController init { // Remove standard controllers @@ -197,6 +200,17 @@ class CustomTransformableNode(transformationSystem: TransformationSystem, object objectManagerChannel ) addTransformationController(customRotationController) + } + if (enableScaling) { + customScaleController = CustomScaleController( + this, + transformationSystem.pinchRecognizer, + objectManagerChannel + ) + + customScaleController.setMinScale(0.1f); + customScaleController.setMaxScale(4.0f); + addTransformationController(customScaleController) } } } @@ -246,3 +260,26 @@ class CustomRotationController(transformableNode: BaseTransformableNode, gesture super.onEndTransformation(gesture) } } + +class CustomScaleController(transformableNode: BaseTransformableNode, gestureRecognizer: PinchGestureRecognizer, objectManagerChannel: MethodChannel) : + ScaleController(transformableNode, gestureRecognizer) { + + val platformChannel: MethodChannel = objectManagerChannel + + override fun canStartTransformation(gesture: PinchGesture): Boolean { + platformChannel.invokeMethod("onScaleStart", transformableNode.name) + super.canStartTransformation(gesture) + return transformableNode.isSelected + } + + override fun onContinueTransformation(gesture: PinchGesture) { + platformChannel.invokeMethod("onScaleChange", transformableNode.name) + super.onContinueTransformation(gesture) + } + + override fun onEndTransformation(gesture: PinchGesture) { + val serializedLocalTransformation = serializeLocalTransformation(transformableNode) + platformChannel.invokeMethod("onScaleEnd", serializedLocalTransformation) + super.onEndTransformation(gesture) + } +} diff --git a/lib/managers/ar_object_manager.dart b/lib/managers/ar_object_manager.dart index d5e72d33..c26e0a6b 100644 --- a/lib/managers/ar_object_manager.dart +++ b/lib/managers/ar_object_manager.dart @@ -14,6 +14,9 @@ typedef NodePanEndHandler = void Function(String node, Matrix4 transform); typedef NodeRotationStartHandler = void Function(String node); typedef NodeRotationChangeHandler = void Function(String node); typedef NodeRotationEndHandler = void Function(String node, Matrix4 transform); +typedef NodeScaleStartHandler = void Function(String node); +typedef NodeScaleChangeHandler = void Function(String node); +typedef NodeScaleEndHandler = void Function(String node, Matrix4 transform); /// Manages the all node-related actions of an [ARView] class ARObjectManager { @@ -31,6 +34,9 @@ class ARObjectManager { NodeRotationStartHandler? onRotationStart; NodeRotationChangeHandler? onRotationChange; NodeRotationEndHandler? onRotationEnd; + NodeScaleStartHandler? onScaleStart; + NodeScaleChangeHandler? onScaleChange; + NodeScaleEndHandler? onScaleEnd; ARObjectManager(int id, {this.debug = false}) { _channel = MethodChannel('arobjects_$id'); @@ -103,6 +109,28 @@ class ARObjectManager { onRotationEnd!(tappedNodeName, transform); } break; + case 'onScaleStart': + if (onScaleStart != null) { + final tappedNode = call.arguments as String; + onScaleStart!(tappedNode); + } + break; + case 'onScaleChange': + if (onScaleChange != null) { + final tappedNode = call.arguments as String; + onScaleChange!(tappedNode); + } + break; + case 'onScaleEnd': + if (onScaleEnd != null) { + final tappedNodeName = call.arguments["name"] as String; + final transform = + MatrixConverter().fromJson(call.arguments['transform'] as List); + + // Notify callback + onScaleEnd!(tappedNodeName, transform); + } + break; default: if (debug) { print('Unimplemented method ${call.method} '); @@ -136,7 +164,9 @@ class ARObjectManager { } else { return await _channel.invokeMethod('addNode', node.toMap()); } + print("before exception"); } on PlatformException catch (e) { + print("add exception"); return false; } } diff --git a/lib/managers/ar_session_manager.dart b/lib/managers/ar_session_manager.dart index 6ac28144..9637f040 100644 --- a/lib/managers/ar_session_manager.dart +++ b/lib/managers/ar_session_manager.dart @@ -84,6 +84,7 @@ class ARSessionManager { bool handleTaps = true, bool handlePans = false, // nodes are not draggable by default bool handleRotation = false, // nodes can not be rotated by default + bool handleScaling = false, // nodes can not be scaled by default }) { _channel.invokeMethod('init', { 'showAnimatedGuide': showAnimatedGuide, @@ -95,6 +96,7 @@ class ARSessionManager { 'handleTaps': handleTaps, 'handlePans': handlePans, 'handleRotation': handleRotation, + 'handleScaling': handleScaling, }); } From 9154a598dfcc34668ec0f527b161cf2ad8565340 Mon Sep 17 00:00:00 2001 From: Sebastian Trattnig Date: Tue, 2 Aug 2022 09:45:06 +0200 Subject: [PATCH 2/2] requested changes --- .../lars/ar_flutter_plugin/AndroidARView.kt | 20 ------------------- .../lars/ar_flutter_plugin/ArModelBuilder.kt | 1 - lib/managers/ar_object_manager.dart | 1 - 3 files changed, 22 deletions(-) diff --git a/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/AndroidARView.kt b/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/AndroidARView.kt index d627c256..d235ac0a 100644 --- a/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/AndroidARView.kt +++ b/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/AndroidARView.kt @@ -661,7 +661,6 @@ internal class AndroidARView( // Get path to given Flutter asset val loader: FlutterLoader = FlutterInjector.instance().flutterLoader() val key: String = loader.getLookupKeyForAsset(dict_node["uri"] as String) - println("FREDTAP2: added model: ") // Add object to scene modelBuilder.makeNodeFromGltf(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling, dict_node["name"] as String, key, dict_node["transformation"] as ArrayList) .thenAccept{node -> @@ -675,25 +674,8 @@ internal class AndroidARView( val runnable = Runnable {sessionManagerChannel.invokeMethod("onError", listOf("1 LOADED RENDERABLE " + node.getRenderable()?.getMaterial().toString() )) } mainHandler.post(runnable) - println("3 LOADED RENDERABLE " + node.getRenderable()?.getMaterial().toString()) - for(i in 1..10){ - println(i) - println(node.getRenderable()?.getMaterial().toString()) - - // node.getRenderable()?.setMaterial(i,node.getRenderable()?.getMaterial(i).setFloat4("baseColorFactor", 1,1,1,1)) - } - /*for(int x = 0; x < node.getRenderable()?.getSubmeshCount(); x++) { - Material m; - m = node.getRenderable()?.getMaterial(x).makeCopy(); - m.setFloat4("baseColorFactor", 1,1,1,1); // changing the color factor works, setFloat("alphaCutoff", cutoffValue); does not seem to work - node.getRenderable()?.setMaterial(x,m); - }*/ - // node.getRenderable()?.getMaterial().setFloat4("baseColorFactor", 1,1,1,1) - println("3a LOADED RENDERABLE " + node.getRenderable()?.getMaterial().toString()) anchorNode.addChild(node) completableFutureSuccess.complete(true) - println("4 LOADED RENDERABLE " + node.getRenderable()?.getSubmeshCount().toString()) - println("5 LOADED RENDERABLE " + node.getRenderable()?.getSubmeshCount().toString()) } else { completableFutureSuccess.complete(false) } @@ -813,8 +795,6 @@ internal class AndroidARView( } catch (e: java.lang.Exception) { completableFutureSuccess.completeExceptionally(e) } - println("FREDTAP: added model: ") - // println("FREDTAP: added model: " + tmp.toString()) return completableFutureSuccess } diff --git a/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/ArModelBuilder.kt b/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/ArModelBuilder.kt index 6eb26ff9..fd927d66 100644 --- a/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/ArModelBuilder.kt +++ b/android/src/main/kotlin/io/carius/lars/ar_flutter_plugin/ArModelBuilder.kt @@ -93,7 +93,6 @@ class ArModelBuilder { // Creates a node form a given gltf model path or URL. The gltf asset loading in Scenform is asynchronous, so the function returns a completable future of type Node fun makeNodeFromGltf(context: Context, transformationSystem: TransformationSystem, objectManagerChannel: MethodChannel, enablePans: Boolean, enableRotation: Boolean, enableScaling: Boolean, name: String, modelPath: String, transformation: ArrayList): CompletableFuture { val completableFutureNode: CompletableFuture = CompletableFuture() - println("MAKENODEFROMGLTF: ") val gltfNode = CustomTransformableNode(transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling) ModelRenderable.builder() diff --git a/lib/managers/ar_object_manager.dart b/lib/managers/ar_object_manager.dart index c26e0a6b..98803167 100644 --- a/lib/managers/ar_object_manager.dart +++ b/lib/managers/ar_object_manager.dart @@ -164,7 +164,6 @@ class ARObjectManager { } else { return await _channel.invokeMethod('addNode', node.toMap()); } - print("before exception"); } on PlatformException catch (e) { print("add exception"); return false;