-
Notifications
You must be signed in to change notification settings - Fork 259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added android pinch gesture #120
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Boolean>("showWorldOrigin") | ||
val argHandleTaps: Boolean? = call.argument<Boolean>("handleTaps") | ||
val argHandleRotation: Boolean? = call.argument<Boolean>("handleRotation") | ||
val argHandleScaling: Boolean? = call.argument<Boolean>("handleScaling") | ||
val argHandlePans: Boolean? = call.argument<Boolean>("handlePans") | ||
val argShowAnimatedGuide: Boolean? = call.argument<Boolean>("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,43 +654,71 @@ internal class AndroidARView( | |
|
||
private fun addNode(dict_node: HashMap<String, Any>, dict_anchor: HashMap<String, Any>? = null): CompletableFuture<Boolean>{ | ||
val completableFutureSuccess: CompletableFuture<Boolean> = 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<Double>) | ||
modelBuilder.makeNodeFromGltf(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling, dict_node["name"] as String, key, dict_node["transformation"] as ArrayList<Double>) | ||
.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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be removed |
||
anchorNode.addChild(node) | ||
completableFutureSuccess.complete(true) | ||
println("4 LOADED RENDERABLE " + node.getRenderable()?.getSubmeshCount().toString()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be removed |
||
println("5 LOADED RENDERABLE " + node.getRenderable()?.getSubmeshCount().toString()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be removed |
||
} 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 )) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we want this hard-coded in here |
||
mainHandler.post(runnable) | ||
|
||
completableFutureSuccess.complete(true) | ||
} | ||
completableFutureSuccess.complete(false) | ||
} | ||
.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<Double>) | ||
modelBuilder.makeNodeFromGlb(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling, dict_node["name"] as String, dict_node["uri"] as String, dict_node["transformation"] as ArrayList<Double>) | ||
.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<Double>) // | ||
modelBuilder.makeNodeFromGlb(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling, dict_node["name"] as String, assetPath as String, dict_node["transformation"] as ArrayList<Double>) // | ||
.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<Double>) | ||
modelBuilder.makeNodeFromGltf(viewContext, transformationSystem, objectManagerChannel, enablePans, enableRotation, enableScaling, dict_node["name"] as String, assetPath, dict_node["transformation"] as ArrayList<Double>) | ||
.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: ") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be removed |
||
// 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<HitResult>() | ||
val planeAndPointHitResults = | ||
allHitResults.filter { ((it.trackable is Plane) || (it.trackable is Point)) } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Double>): CompletableFuture<CustomTransformableNode> { | ||
fun makeNodeFromGltf(context: Context, transformationSystem: TransformationSystem, objectManagerChannel: MethodChannel, enablePans: Boolean, enableRotation: Boolean, enableScaling: Boolean, name: String, modelPath: String, transformation: ArrayList<Double>): CompletableFuture<CustomTransformableNode> { | ||
val completableFutureNode: CompletableFuture<CustomTransformableNode> = CompletableFuture() | ||
|
||
val gltfNode = CustomTransformableNode(transformationSystem, objectManagerChannel, enablePans, enableRotation) | ||
println("MAKENODEFROMGLTF: ") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be removed |
||
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<Double>): CompletableFuture<CustomTransformableNode> { | ||
fun makeNodeFromGlb(context: Context, transformationSystem: TransformationSystem, objectManagerChannel: MethodChannel, enablePans: Boolean, enableRotation: Boolean, enableScaling: Boolean, name: String, modelPath: String, transformation: ArrayList<Double>): CompletableFuture<CustomTransformableNode> { | ||
val completableFutureNode: CompletableFuture<CustomTransformableNode> = 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) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
SebastianAT marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
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<bool>('addNode', node.toMap()); | ||
} | ||
print("before exception"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be removed |
||
} on PlatformException catch (e) { | ||
print("add exception"); | ||
return false; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be removed