1

Introduction

I'm designing an iPhone game in Augmented Reality using ARKit. One of the parts of my game involves the user swiping to throw a ball at a target. My intent is to create a vector with the y value and z value of the target but the x value of where the user swipes. When testing this system, however, the ball always moves towards the left. I believe that it is an issue with the axes I'm using but I am unable to understand how to change this. The target can be anywhere on a plane in the AR world.

A depiction is provided in the image. The user swipes from the ball and the ball should move towards the y and z coordinates of the target and the x value of wherever the swipe ends.

enter image description here

Current Code

Most of the code I have used is based on the following Tutorial on RayWenderlich.com.

I start by implementing the touchesBegan and touchesEnded functions as follow to calculate the end point of the swipe and how quick the swipe was:

var startTouch: UITouch?
var endTouch: UITouch?
var startTouchTime: TimeInterval!
var endTouchTime: TimeInterval!

 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    if self.currentMode == .throwingProjectiles {

        guard let firstTouch = touches.first else { return }
        let point = firstTouch.location(in: sceneView)
        let hitResults = sceneView.hitTest(point, options: [:])

        if hitResults.first?.node == projectileIndicator {
            startTouch = touches.first
            startTouchTime = Date().timeIntervalSince1970
        }  
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

    guard startTouch != nil else { return }

    endTouch = touches.first
    endTouchTime = Date().timeIntervalSince1970
    throwBall()
}

I then implement the throwBall() function as follows:

func throwBall() {

    guard let endingTouch = endTouch else { return }

    let firstTouchResult = sceneView.hitTest(
        endingTouch.location(in: sceneView),
        options: nil 
        ).filter({
            $0.node == targetNode || $0.node == floorNode
        }).first


    guard let touchResult = firstTouchResult else {return}

    let vp = endingTouch.location(in: sceneView)
    let vpWithDepth = SCNVector3Make(Float(vp.x), Float(vp.y), 0)
    let scenePoint = sceneView.unprojectPoint(vpWithDepth)

    let timeDifference = ( endTouchTime - startTouchTime)/4
    let velocityComponent = Float(min(max(1 - timeDifference, 0.1), 10.0))

    let impulseVector = SCNVector3(
        x: scenePoint.x,
        y: targetNode.worldPosition.y + 1.005 * velocityComponent * 5,
        z: targetNode.worldPosition.z * velocityComponent * 10
    )
    spawnProjectile()
    currentBallNode?.name = "Ball"
    balls.append(currentBallNode!)
    currentBallNode?.physicsBody?.applyForce(impulseVector, asImpulse: true)

    currentBallNode = nil
    startTouchTime = nil
    endTouchTime = nil
    startTouch = nil
    endTouch = nil 
}

The unproject point code is from here: https://stackoverflow.com/a/25154025/6642130

So my question is, how do I facilitate this throw?

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
ymulki
  • 432
  • 2
  • 15

1 Answers1

1

I fixed the swipe, this seems perfect. I changed my answer when I found out how to perfectly do it. I use a PanGestureRecognizer which is dragged into the sceneView, my ARview.

@objc func handlePan(recognizer : UIPanGestureRecognizer) {
    guard let food = foodToShot else {return}
    //If removes the finger or something
    if recognizer.state == .cancelled {
        food.removeFromParentNode()
    //If it actually shoots
    } else if recognizer.state == .ended {
        //I do something only when direction makes sense - MARK: MAYBE STUDY THIS
        switch recognizer.direction {
        case .bottomToTop :
            //Takes swipe velocity in CGFloat/seconds
            let velocity = recognizer.velocity(in: sceneView)
            //Calculate V
            let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
            //Adjustements to have a decent not too fast velocity
            let slideMultiplier = magnitude / 200
            let slideFactor = 0.1 * slideMultiplier
            //I save current frame
            let currentFrame = self.sceneView.session.currentFrame!
            //Creation of physicalBody
            let foodPhysicsBody = SCNPhysicsBody(
                type: .dynamic,
                shape: SCNPhysicsShape(geometry: SCNSphere(radius: 0.04))
            )
            foodPhysicsBody.isAffectedByGravity = true
            //Collisions
            foodPhysicsBody.categoryBitMask = CollisionCategory.food.rawValue
            foodPhysicsBody.categoryBitMask = CollisionCategory.floor.rawValue
            foodPhysicsBody.contactTestBitMask = CollisionCategory.food.rawValue
            foodPhysicsBody.mass = 3
            foodPhysicsBody.restitution = 0.75
            foodPhysicsBody.damping = 0.1
            foodPhysicsBody.friction = 2
            food.physicsBody = foodPhysicsBody
            //Calculate with my V and Vx and my acot modified function
            let closeTranslation = simd_make_float4(0,Float(acotForSwipes(x: Double(magnitude / velocity.x))),-1.2,0)
            let rotatedForce = simd_mul(currentFrame.camera.transform, closeTranslation)
            //Calculate force
            let force = SCNVector3Make(rotatedForce.x, rotatedForce.y, rotatedForce.z)
            //I give force to physicBody
            food.physicsBody?.applyForce(force * SCNFloat(slideFactor) * 15, asImpulse: true)
            //I don't care about other swipes
        case .leftToRight:
            return
        case .rightToLeft:
            return
        case .topToBottom:
            return
        case .undefined:
            return
        }
    }
}

this is my arctangent function, modified for the swipes:

//Arcotangent modified to suit my swipe needs
func acotForSwipes(x : Double) -> Double {
    if x > 1.0 || x < -1.0{
        return atan(1.0/x)
    } else {
        return .pi/2 - atan(x)
    }
}

And lastly, if you care, this is an extention for the panGestureRecognizer

    public enum UIPanGestureRecognizerDirection {
    case undefined
    case bottomToTop
    case topToBottom
    case rightToLeft
    case leftToRight
}
public enum TransitionOrientation {
    case unknown
    case topToBottom
    case bottomToTop
    case leftToRight
    case rightToLeft
}


extension UIPanGestureRecognizer {
    public var direction: UIPanGestureRecognizerDirection {
        let velocity = self.velocity(in: view)
        let isVertical = abs(velocity.y) > abs(velocity.x)

        var direction: UIPanGestureRecognizerDirection

        if isVertical {
            direction = velocity.y > 0 ? .topToBottom : .bottomToTop
        } else {
            direction = velocity.x > 0 ? .leftToRight : .rightToLeft
        }

        return direction
    }

    public func isQuickSwipe(for orientation: TransitionOrientation) -> Bool {
        let velocity = self.velocity(in: view)
        return isQuickSwipeForVelocity(velocity, for: orientation)
    }

    private func isQuickSwipeForVelocity(_ velocity: CGPoint, for orientation: TransitionOrientation) -> Bool {
        switch orientation {
        case .unknown : return false
        case .topToBottom : return velocity.y > 1000
        case .bottomToTop : return velocity.y < -1000
        case .leftToRight : return velocity.x > 1000
        case .rightToLeft : return velocity.x < -1000
        }
    }
}

extension UIPanGestureRecognizer {
    typealias GestureHandlingTuple = (gesture: UIPanGestureRecognizer? , handle: (UIPanGestureRecognizer) -> ())
    fileprivate static var handlers = [GestureHandlingTuple]()

    public convenience init(gestureHandle: @escaping (UIPanGestureRecognizer) -> ()) {
        self.init()
        UIPanGestureRecognizer.cleanup()
        set(gestureHandle: gestureHandle)
    }

    public func set(gestureHandle: @escaping (UIPanGestureRecognizer) -> ()) {
        weak var weakSelf = self
        let tuple = (weakSelf, gestureHandle)
        UIPanGestureRecognizer.handlers.append(tuple)
        addTarget(self, action: #selector(handleGesture))
    }

    fileprivate static func cleanup() {
        handlers = handlers.filter { $0.0?.view != nil }
    }

    @objc private func handleGesture(_ gesture: UIPanGestureRecognizer) {
        let handleTuples = UIPanGestureRecognizer.handlers.filter{ $0.gesture === self }
        handleTuples.forEach { $0.handle(gesture)}
    }
}



    extension UIPanGestureRecognizerDirection {
        public var orientation: TransitionOrientation {
            switch self {
            case .rightToLeft: return .rightToLeft
            case .leftToRight: return .leftToRight
            case .bottomToTop: return .bottomToTop
            case .topToBottom: return .topToBottom
            default: return .unknown
            }
        }
    }

    extension UIPanGestureRecognizerDirection {
        public var isHorizontal: Bool {
            switch self {
            case .rightToLeft, .leftToRight:
                return true
            default:
                return false
            }
        }
    }

If someone finds this answer helpful let me know, this is my first time answering :)