Skip to content
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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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")

Expand All @@ -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))
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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: ")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be removed

// 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())
Copy link
Owner

Choose a reason for hiding this comment

The 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())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be removed

println("5 LOADED RENDERABLE " + node.getRenderable()?.getSubmeshCount().toString())
Copy link
Owner

Choose a reason for hiding this comment

The 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 )) }
Copy link
Owner

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -776,7 +813,8 @@ internal class AndroidARView(
} catch (e: java.lang.Exception) {
completableFutureSuccess.completeExceptionally(e)
}

println("FREDTAP: added model: ")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be removed

// println("FREDTAP: added model: " + tmp.toString())
return completableFutureSuccess
}

Expand All @@ -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)) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: ")
Copy link
Owner

Choose a reason for hiding this comment

The 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(
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}
Expand Down Expand Up @@ -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)
}
}
30 changes: 30 additions & 0 deletions lib/managers/ar_object_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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');
Expand Down Expand Up @@ -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} ');
Expand Down Expand Up @@ -136,7 +164,9 @@ class ARObjectManager {
} else {
return await _channel.invokeMethod<bool>('addNode', node.toMap());
}
print("before exception");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be removed

} on PlatformException catch (e) {
print("add exception");
return false;
}
}
Expand Down
2 changes: 2 additions & 0 deletions lib/managers/ar_session_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>('init', {
'showAnimatedGuide': showAnimatedGuide,
Expand All @@ -95,6 +96,7 @@ class ARSessionManager {
'handleTaps': handleTaps,
'handlePans': handlePans,
'handleRotation': handleRotation,
'handleScaling': handleScaling,
});
}

Expand Down