1

I have a node object in 3d view and i need to drag that object, So far i have tried from here : Placing, Dragging and Removing SCNNodes in ARKit

and converted in swift

@objc func handleDragGesture(_ gestureRecognizer: UIGestureRecognizer) {
        let tapPoint = gestureRecognizer.location(in: self.sceneView)
        switch gestureRecognizer.state {
        case .began:
            print("Object began to move")
            let hitResults = self.sceneView.hitTest(tapPoint, options: nil)
            if hitResults.isEmpty { return }
            let hitResult = hitResults.first
            if let node = hitResult?.node.parent?.parent?.parent {
                self.photoNode = node
            }
        case .changed:
            print("Moving object position changed")
            if let _ = self.photoNode {
                let hitResults = self.sceneView.hitTest(tapPoint, types: .featurePoint)
                let hitResult = hitResults.last
                if let transform = hitResult?.worldTransform {
                    let matrix = SCNMatrix4FromMat4(transform)
                    let vector = SCNVector3Make(matrix.m41, matrix.m42, matrix.m43)
                    self.photoNode?.position = vector
                }
            }
        case .ended:
            print("Done moving object")
        default:
            break
        }
    }

but it is not working properly. what is the correct way to do?

Abhishek Thapliyal
  • 3,497
  • 6
  • 30
  • 69
  • I'm the one you linked in your question. It could be possible that your node has even more parents. At the time i just kept going up parent levels until it worked, so many you're still not grabbing the entire SCNNode. Try giving your node a name when you add it to the scene and debug what node you're getting from your hitresult, check if it's name is the same as the one you gave, and if not, go up a parent level till you find it. I'm sure there's a cleaner way to go about this, but I didn't have the time to do it then. – Alan Jan 08 '18 at 14:36
  • @AlanS : it's a basic view i have added a single node in sceneView that's it. All i need to do is dragging node – Abhishek Thapliyal Jan 08 '18 at 14:52
  • Are you sure that your node is not nil in that case? maybe you are skipping the object itself by trying to get it's parent and you're ending up with nil or the rootNode of the entire scene. Add a breakpoint and check for the value of node after you've set it. – Alan Jan 08 '18 at 15:56
  • what ever the code is, i added already. you can identify what i have done – Abhishek Thapliyal Jan 08 '18 at 16:02
  • Yes exactly, but my code worked cause my nodes had several child nodes. You need to check to see whether your node is actually what you want it to be. Use breakpoints to see what the value is. Or try just doing hitResult?.node, if that doesn't work then use hitResult?.node.parent, etc.. until it works. – Alan Jan 08 '18 at 16:04
  • @AlanS : Can you just try with single node? It will be great help – Abhishek Thapliyal Jan 08 '18 at 16:08
  • Have you tried changing the implementation at all? – Alan Jan 09 '18 at 09:26
  • i tried in coordinates but couldn't find the right things – Abhishek Thapliyal Jan 09 '18 at 12:25
  • try this: [link](https://stackoverflow.com/a/48220751/5567142) – Alok Subedi Jan 26 '18 at 10:28

2 Answers2

3

You can do this using panGestureRecongniser... see basic swift Playground code for handling a SCNNode.

import UIKit
import ARKit
import SceneKit
import PlaygroundSupport

public var textNode : SCNNode?

// Main ARKIT ViewController
class ViewController : UIViewController, ARSCNViewDelegate, ARSessionDelegate {

var textNode: SCNNode!
var counter = 0
@IBOutlet var sceneView: ARSCNView!

override func viewDidLoad() {
    super.viewDidLoad()
    // set the views delegate
    sceneView.delegate = self as! ARSCNViewDelegate
    // show statistics such as fps and timing information
    sceneView.showsStatistics = true
    // Create a new scene 
    sceneView.scene.rootNode
    // Add ligthing
    sceneView.autoenablesDefaultLighting = true

    let text = SCNText(string: "Drag Me with Pan Gesture!", extrusionDepth: 1)
    //  create material
    let material = SCNMaterial()
    material.diffuse.contents = UIColor.green
    text.materials = [material]

    //Create Node object
    textNode = SCNNode()

    textNode.name =  "textNode"
    textNode.scale = SCNVector3(x:0.004,y:0.004,z:0.004)
    textNode.geometry = text
    textNode.position = SCNVector3(x: 0, y:0.02, z: -1)

    //  add new node to root node
    self.sceneView.scene.rootNode.addChildNode(textNode)

    // Add pan gesture for dragging the textNode about
    sceneView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:))))

}

override func loadView() {

    sceneView = ARSCNView(frame:CGRect(x: 0.0, y: 0.0, width: 500.0, height: 600.0))
    // Set the view's delegate
    sceneView.delegate = self 

    let config = ARWorldTrackingConfiguration()
    config.planeDetection = .horizontal

    // Now we'll get messages when planes were detected...
    sceneView.session.delegate = self

    self.view = sceneView
    sceneView.session.run(config)

}

@objc func panGesture(_ gesture: UIPanGestureRecognizer) {     

    gesture.minimumNumberOfTouches = 1

    let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)
    guard let result: ARHitTestResult = results.first else {
        return
    }

    let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
    textNode.position = position
} 

}

PlaygroundPage.current.liveView = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true 

EDIT:

The above drag function only worked if you had 1 object in the view, so it was not really necessary to hit the node to start dragging. It will just drag from where ever you tapped on the screen. If you have multiple objects in the view, and you want to drag nodes independently. You could change the panGesture function to the following, detect each node tapped first:

// drags nodes independently

@objc func panGesture(_ gesture: UIPanGestureRecognizer) {     

    gesture.minimumNumberOfTouches = 1

    let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)

    guard let result: ARHitTestResult = results.first else {
        return
    }

    let hits = self.sceneView.hitTest(gesture.location(in: gesture.view), options: nil)
    if let tappedNode = hits.first?.node {
        let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
        tappedNode.position = position
    }

} 
Clay
  • 1,721
  • 2
  • 10
  • 18
  • 1
    this is same i tried but its not working, the node is coming more towrds and drag location is resetting when retap on it – Abhishek Thapliyal Jan 07 '18 at 13:17
  • I’ve edited my answer to drag only if you tap on a node. Before it would drag a node even if you didn’t tap on the node (as my example only used 1 node). I’m not following your comment “the node is coming more towards” could you rephrase that better? Do you mean that the node is moving more towards the camera position when dragging? The hit result is the featurePoint.. there are 4 different types to choose from here https://developer.apple.com/documentation/arkit/arhittestresult.resulttype you could try using another resultType, if your drag object is on a detected plane. – Clay Jan 07 '18 at 21:05
  • still issue is same – Abhishek Thapliyal Jan 08 '18 at 05:55
  • Can you be more explicit what the drag needs to do exactly? – Clay Jan 08 '18 at 05:58
  • i want to move that node along drag – Abhishek Thapliyal Jan 08 '18 at 06:00
  • 1
    I don’t follow. Are you dragging the object along the horizontal plane? Does it matter if the object lifts as the finger moves across the display?? Need to be more explicit with what your asking. – Clay Jan 08 '18 at 06:02
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/162716/discussion-between-abhishek-thapliyal-and-ashley72). – Abhishek Thapliyal Jan 08 '18 at 06:03
  • 2
    This works but very badly, the object jumps all around. You are hit testing against .featurePoints so as you move the finger.. if it doesnt hit any featurepoint then it wont move, and then.. if with luck you hit a point.. it jumps. This is just not usable. – omarojo May 02 '18 at 09:20
  • Dragging along axis -> https://stackoverflow.com/questions/46825140/arkit-drag-a-node-along-a-specific-axis-not-on-a-plane. I’ve also come seen answers for dragging along the plane. – Clay May 02 '18 at 09:38
1

REF: https://stackoverflow.com/a/48220751/5589073

This code works for me

private func drag(sender: UIPanGestureRecognizer) {

    switch sender.state {
    case .began:
        let location = sender.location(in: self.sceneView)
        guard let hitNodeResult = self.sceneView.hitTest(location,
                                                         options: nil).first else { return }
        self.PCoordx = hitNodeResult.worldCoordinates.x
        self.PCoordy = hitNodeResult.worldCoordinates.y
        self.PCoordz = hitNodeResult.worldCoordinates.z
    case .changed:
        // when you start to pan in screen with your finger
        // hittest gives new coordinates of touched location in sceneView
        // coord-pcoord gives distance to move or distance paned in sceneview
        let hitNode = sceneView.hitTest(sender.location(in: sceneView), options: nil)
        if let coordx = hitNode.first?.worldCoordinates.x,
            let coordy = hitNode.first?.worldCoordinates.y,
            let coordz = hitNode.first?.worldCoordinates.z {
            let action = SCNAction.moveBy(x: CGFloat(coordx - self.PCoordx),
                                          y: CGFloat(coordy - self.PCoordy),
                                          z: CGFloat(coordz - self.PCoordz),
                                          duration: 0.0)
            self.photoNode.runAction(action)

            self.PCoordx = coordx
            self.PCoordy = coordy
            self.PCoordz = coordz
        }

        sender.setTranslation(CGPoint.zero, in: self.sceneView)
    case .ended:
        self.PCoordx = 0.0
        self.PCoordy = 0.0
        self.PCoordz = 0.0
    default:
        break
    }
}
Abhishek Thapliyal
  • 3,497
  • 6
  • 30
  • 69