How to detect if SCNBox was touched in scenekit? - ios

How to detect if SCNBox was touched in scenekit? I am building a VR app

you can use this:
let tapGR = UITapGestureRecognizer(target: self, action: #selector(tapGesture(sender:)))
sceneView.addGestureRecognizer(tapGR)
#objc func tapGesture(sender: UITapGestureRecognizer) {
let location: CGPoint = (sender.location(in: self.sceneView))
let hits = self.sceneView.hitTest(location, options: nil)
if let tappedNode : SCNNode = hits.first?.node {
tappedNode.position.y += 0.5
print(tappedNode)
}
}

Related

iOS 11 ARKit : Drag Object in 3D View

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?
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
}
}
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
}
}

Moving a node in SceneKit using touch

I am trying to move a node in SceneKit by touch.
I am using the code here: Move a specific node in SceneKit using touch
The issue that I am having is that every time I start a panGesture, the object I touch goes to the left corner of the scene to start the move. Moving it from that position and releasing are ok, just the issue that at every start of panning, the object resets from the left corner. If I zoom out, it resets itself from the corner of this new zoom level.
My code is:
func CGPointToSCNVector3(view: SCNView, depth: Float, point: CGPoint) -> SCNVector3 {
let projectedOrigin = view.projectPoint(SCNVector3Make(0, 0, depth))
let locationWithz = SCNVector3Make(Float(point.x), Float(point.y), projectedOrigin.z)
return view.unprojectPoint(locationWithz)
}
func dragObject(sender: UIPanGestureRecognizer){
if(movingNow){
let translation = sender.translationInView(sender.view!)
var result : SCNVector3 = CGPointToSCNVector3(objectView, depth: tappedObjectNode.position.z, point: translation)
tappedObjectNode.position = result
}
else{
let hitResults = objectView.hitTest(sender.locationInView(objectView), options: nil)
if hitResults.count > 0 {
movingNow = true
}
}
if(sender.state == UIGestureRecognizerState.Ended) {
}
}
and
override func viewDidLoad() {
super.viewDidLoad()
let scnView = objectView
scnView.backgroundColor = UIColor.whiteColor()
scnView.autoenablesDefaultLighting = true
scnView.allowsCameraControl = true
i am temporarily disabling the allowsCameraControl's panning functions before dragObject is called by doing this:
globalPanRecognizer = UIPanGestureRecognizer(target: self,
action:#selector(ViewController.dragObject(_:)))
objectView.addGestureRecognizer(globalPanRecognizer)
These are the values inside the first call to CGPointToSCNVector3 :
initial value of tappedObjectNode: SCNVector3(x: 0.100000001, y: 0.100000001, z: 3.0)
projectedOrigin : SCNVector3(x: 261.613159, y: 285.530396, z: 0.949569583) - this is abnormally big
value returned by CGPointToSCNVector3 : SCNVector3(x: 1.03418088, y: 1.9734658, z: 4.64346933)
I have played with different variations of CGPointToSCNVector3 but no luck.
What is the cause of this behavior?
Thanks,
The solution was to change sender.translationInView(sender.view!) to sender.translationInView(self.view!)
Swift 4.1 / Xcode 9.3 / iOS 11.3
// node that you want the user to drag
var tappedObjectNode = SCNNode()
// the SCNView
#IBOutlet weak var sceneView: SCNView!
// the scene
let scene = SCNScene()
//helper
func CGPointToSCNVector3(view: SCNView, depth: Float, point: CGPoint) -> SCNVector3 {
let projectedOrigin = view.projectPoint(SCNVector3Make(0, 0, depth))
let locationWithz = SCNVector3Make(Float(point.x), Float(point.y), projectedOrigin.z)
return view.unprojectPoint(locationWithz)
}
// gesture handler
var movingNow = false
#objc func dragObject(sender: UIPanGestureRecognizer){
if(movingNow){
let translation = sender.translation(in: sender.view!)
var result : SCNVector3 = CGPointToSCNVector3(view: sceneView, depth: tappedObjectNode.position.z, point: translation)
tappedObjectNode.position = result
} else {
// view is the view containing the sceneView
let hitResults = sceneView.hitTest(sender.location(in: view), options: nil)
if hitResults.count > 0 {
movingNow = true
}
}
}
// in viewDidLoad
sceneView.scene = scene
let panner = UIPanGestureRecognizer(target: self, action: #selector(dragObject(sender:)))
sceneView.addGestureRecognizer(panner)

Convert touch location of UITapGestureRecognizer for SpriteKit - Swift

I created a game with a SceneKit scene into a UIViewController.
I implemented a UITapGestureRecognizer like this :
// TAP GESTURE
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTap:")
sceneView.overlaySKScene?.view!.addGestureRecognizer(tapGesture)
And the handleTap function :
func handleTap(sender: UIGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Ended {
var touchLocation = sender.locationInView(sender.view)
touchLocation = self.view.convertPoint(touchLocation, toView: self.view)
let touchedNode = sceneView.overlaySKScene?.nodeAtPoint(touchLocation)
if touchedNode == myNode {
// DO SOMETHING
}
}
}
Imagine that myNode's position is (x: 150.0, y: 150.0).
The problem is that the touchPosition.y is at the opposite of the y position of myNode's y position.
How can I invert y position of the touch ?
Thanks !
Try this instead, its accurate for me:-
override func didMoveToView(view: SKView) {
let tap:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tap:"))
tap.numberOfTapsRequired = 1
view.addGestureRecognizer(tap)
}
func tap(sender:UITapGestureRecognizer){
if sender.state == .Ended {
var touchLocation: CGPoint = sender.locationInView(sender.view)
touchLocation = self.convertPointFromView(touchLocation)
}
}

How to get the start point of UIPanGesture and constantly calculate the moving distance?

How to get the start point location of UIPanGesture and constantly calculate the moving distance? It would be better if you can provide some Swift code in your answer.
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: "catchPaned")
self.view.addGestureRecognizer(pan)
self.view.userInteractionEnabled = true
}
func catchPaned(gesture:UIPanGestureRecognizer){
switch(gesture.state){
case .Began:
let touchStart = gesture.locationInView(self.view)
case .Changed:
let distance = gesture.translationInView(self.view)
default:
println("default")
}
}
Get start point using locationInView,get distance using translationInView
I figure it out myself.
let panGesture = UIPanGestureRecognizer(target: self, action: Selector("panFunction:"))
myView.addGestureRecognizer(panGesture)
func panFunction(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(self.view)
let startPoint = CGPointZero
let distance = Double(translation.y - startPoint.y)
println("\(distance)")
}

How do you transition back and forth between dae scenes?

Here is a slightly modified starter SceneKit project--I added another .dae file named planet.dae. Tap the "ship" to switch to planet.dae. Tap the planet to switch back to ship.dae. Tapping ship does open planet.dae. But when user taps the planet, nothing happens--doesn't seem to recognize my touch. What gives?
import SceneKit
class GameViewController: UIViewController {
override func viewDidLoad() { super.viewDidLoad()
shipScene()
}
func shipScene() {
let scene = SCNScene(named: "art.scnassets/ship.dae")!
let ship = scene.rootNode.childNodeWithName("ship", recursively: true)!
let scnView = self.view as SCNView
scnView.scene = scene
scnView.autoenablesDefaultLighting = true
scnView.allowsCameraControl = true
scnView.backgroundColor = UIColor.blackColor()
let tapGesture = UITapGestureRecognizer(target: self, action: "handleShipTap:")
let gestureRecognizers = NSMutableArray()
gestureRecognizers.addObject(tapGesture)
if let existingGestureRecognizers = scnView.gestureRecognizers {
gestureRecognizers.addObjectsFromArray(existingGestureRecognizers)
}
scnView.gestureRecognizers = gestureRecognizers
}
func handleShipTap(gestureRecognize: UIGestureRecognizer) {
let scnView = self.view as SCNView
let p = gestureRecognize.locationInView(scnView)
if let hitResults = scnView.hitTest(p, options: nil) {
if hitResults.count > 0 {
let result: AnyObject! = hitResults[0]
if result.node!.name!.hasPrefix("ship") {
planetScene()
}
}
}
}
func planetScene() {
let scene = SCNScene(named: "art.scnassets/saturn.dae")!
let ship = scene.rootNode.childNodeWithName("planet", recursively: true)!
let scnView = self.view as SCNView
scnView.scene = scene
scnView.autoenablesDefaultLighting = true
scnView.allowsCameraControl = true
scnView.backgroundColor = UIColor.blackColor()
let tapGesture = UITapGestureRecognizer(target: self, action: "handlePlanetTap:")
let gestureRecognizers = NSMutableArray()
gestureRecognizers.addObject(tapGesture)
if let existingGestureRecognizers = scnView.gestureRecognizers {
gestureRecognizers.addObjectsFromArray(existingGestureRecognizers)
}
scnView.gestureRecognizers = gestureRecognizers
}
func handlePlanetTap(gestureRecognize: UIGestureRecognizer) {
let scnView = self.view as SCNView
let p = gestureRecognize.locationInView(scnView)
if let hitResults = scnView.hitTest(p, options: nil) {
if hitResults.count > 0 {
let result: AnyObject! = hitResults[0]
if result.node!.name!.hasPrefix("saturn") {
shipScene()
}
}
}
}
}
Removing the tapGesture from the planetScene() is all.

Resources