1

I am working on an augmented reality app and I would like to be able to drag an object in the space. The problem with the solutions I find here in SO, the ones that suggest using projectPoint/unprojectPoint, is that they produce movement along the XY plane.

I was trying to use the fingers movement on the screen as an offset for x and z coordinates of the node. The problem is that there is a lot of stuff to take in consideration (camera's position, node's position, node's rotation, etc..)

Is there a simpler way of doing this?

picciano
  • 22,341
  • 9
  • 69
  • 82
kemkriszt
  • 280
  • 3
  • 17

3 Answers3

3

I have updated @Alok answer as in my case it is drgging in x plane only from above solution. So i have added y coordinates, working for me.

var PCoordx: Float = 0.0
var PCoordy: Float = 0.0
var PCoordz: Float = 0.0

@objc func handleDragGesture(_ sender: UIPanGestureRecognizer) {

    switch sender.state {
    case .began:
        let hitNode = self.sceneView.hitTest(sender.location(in: self.sceneView),
                                             options: nil)
        self.PCoordx = (hitNode.first?.worldCoordinates.x)!
        self.PCoordy = (hitNode.first?.worldCoordinates.y)!
        self.PCoordz = (hitNode.first?.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 - PCoordx),
                                          y: CGFloat(coordy - PCoordy),
                                          z: CGFloat(coordz - 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
1

first you need to create floor or very large plane few meters (i have 10) below origin. This makes sure your hittest always returns value. Then using pan gesture :

//store previous coordinates from hittest to compare with current ones
var PCoordx: Float = 0.0
var PCoordz: Float = 0.0

@objc func move(_ gestureRecognizer: UIPanGestureRecognizer){
    if gestureRecognizer.state == .began{
        let hitNode = sceneView.hitTest(gestureRecognizer.location(in: sceneView), options: nil)
        PCoordx = (hitNode.first?.worldCoordinates.x)!
        PCoordz = (hitNode.first?.worldCoordinates.z)!
    }

    // 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 
    if gestureRecognizer.state == .changed {
        let hitNode = sceneView.hitTest(gestureRecognizer.location(in: sceneView), options: nil)
        if let coordx = hitNode.first?.worldCoordinates.x{
            if let coordz = hitNode.first?.worldCoordinates.z{

            let action = SCNAction.moveBy(x: CGFloat(coordx-PCoordx), y: 0, z: CGFloat(coordz-PCoordz), duration: 0.1)
            node.runAction(action)

            PCoordx = coordx
            PCoordz = coordz
            }
        }

        gestureRecognizer.setTranslation(CGPoint.zero, in: sceneView)
    }
    if gestureRecognizer.state == .ended{
        PCoordx = 0
        PCoordz = 0
    }
}

In my case there is only one node so i have not checked if required node is taped or not. You can always check for it if you have many nodes.

Alok Subedi
  • 1,601
  • 14
  • 26
-1

If I understand you correctly, I do this using a UIPanGestureRecognizer added to the ARSCNView.

In my case I want to check if pan was started on a given virtual object and keep track of which it was because I can have multiple, but if you have just one object you may not need the targetNode variable. The 700 constant I use to divide I got it by trial and error to make the translation smoother, you may need to change it for your case.

Moving finger up, moves the object further away from camera and moving it down moves it nearer. Horizontal movement of fingers moves object left/right.

@objc func onTranslate(_ sender: UIPanGestureRecognizer) {
    let position = sender.location(in: scnView)
    let state = sender.state

    if (state == .failed || state == .cancelled) {
        return
    }

    if (state == .began) {
        // Check pan began on a virtual object
        if let objectNode = virtualObject(at: position).node {
            targetNode = objectNode
            latestTranslatePos = position
        }
    }
    else if let _ = targetNode {

        // Translate virtual object
        let deltaX = Float(position.x - latestTranslatePos!.x)/700
        let deltaY = Float(position.y - latestTranslatePos!.y)/700

        targetNode!.localTranslate(by: SCNVector3Make(deltaX, 0.0, deltaY))
        latestTranslatePos = position

        if (state == .ended) {
            targetNode = nil
        }
    }
leandrodemarco
  • 1,552
  • 1
  • 15
  • 22