ARKit – Drag a node along a specific axis (not on a plane) - ios

I am trying to drag a node exactly where my finger is on the screen on the Y-axis. But I don't find any way to do it, here is my current code. Do you have any idea?
var movedObject:SCNNode?
#objc func handlePan(_ recognizer: UIPanGestureRecognizer) {
if recognizer.state == .began {
let tapPoint: CGPoint = recognizer.location(in: sceneView)
let result = sceneView.hitTest(tapPoint, options: nil)
if result.count == 0 {
return
}
let hitResult: SCNHitTestResult? = result.first
movedObject = hitResult?.node //.parent?.parent
} else if recognizer.state == .changed {
if (movedObject != nil) {
let tapPoint: CGPoint = recognizer.location(in: sceneView)
let hitResults = sceneView.hitTest(tapPoint, types: .featurePoint)
let result: ARHitTestResult? = hitResults.last
if result?.worldTransform != nil{
let matrix: SCNMatrix4 = SCNMatrix4.init((result?.worldTransform)!)
let vector: SCNVector3 = SCNVector3Make(matrix.m41, matrix.m42, matrix.m43)
movedObject?.position = vector
}
}
} else if recognizer.state == .ended {
movedObject = nil
}
}

Found the solution ! I added some explanations in comments.
if (movedObject != nil) {
//Normalized-depth coordinate matching the plane I want
let projectedOrigin = sceneView.projectPoint((movedObject?.position)!)
//Location of the finger in the view on a 2D plane
let location2D = recognizer.location(in: sceneView)
//Location of the finger in a 3D vector
let location3D = SCNVector3Make(Float(location2D.x), Float(location2D.y), projectedOrigin.z)
//Unprojects a point from the 2D pixel coordinate system of the renderer to the 3D world coordinate system of the scene
let realLocation3D = sceneView.unprojectPoint(location3D)
if movedObject?.position != nil {
//Only updating Y axis position
movedObject?.position = SCNVector3Make((movedObject?.position.x)!, realLocation3D.y, (movedObject?.position.z)!)
}
}
Posts that help understand:
unProjectPoint & projectedOrigin
translation & unProjectPoint

Related

SceneKit finger panning becomes inaccurate as it moves away from center

In my ARSCNView, I'm drawing a SCNPlane with a grid inside. It is a child of sceneView.pointOfView. When you drag your finger across it, you can connect the dots in the grid. This works almost perfectly, except something subtly strange is happening: The edge of the tragged line SHOULD track accurately with the finger, but it actually falls ahead or behind depending on how far above or below the center it is respectively.
#objc func handlePan(gesture: UIPanGestureRecognizer) {
print("panned")
let location = gesture.location(in: sceneView)
let projectedOriginUI = sceneView.projectPoint(SCNVector3(0, 0, gridCon.grid.position.z))
let placeOnUI = sceneView.unprojectPoint(SCNVector3(location.x, location.y, CGFloat(projectedOriginUI.z)))
let rootNode: SCNNode = sceneView.scene.rootNode
let positionOnGrid = rootNode.convertPosition(placeOnUI, to: gridCon.grid)
let state = gesture.state
let hitVertex = gridCon.hitVertex(location: location, sceneView: sceneView)
if (state == UIGestureRecognizer.State.began) {
print("began")
if (hitVertex != nil) {
let edge = EdgeController.createEdge()
edge.position.x = hitVertex?.position.x ?? 0
edge.position.y = hitVertex?.position.y ?? 0
gridCon.grid.addChildNode(edge)
draggedLine = edge
startVertex = hitVertex
print("Edge added!")
}
}
if (state == UIGestureRecognizer.State.ended) {
draggedLine = nil
startVertex = nil
}
if (draggedLine != nil && startVertex !== nil) {
print("line height changed")
let _draggedLine = draggedLine!
let geo: SCNPlane = _draggedLine.geometry as! SCNPlane
let between = startVertex!.position.y - positionOnGrid.y
geo.height = CGFloat(between)
_draggedLine.position.y = startVertex!.position.y - (between/2)
}
}
Why is my finger tracking slightly off here?

How to scale and move all nodes at once? ARKit Swift

I got these functions that scale and move the 3D object displayed on the screen when tapping on it. The problem consists that if I move or scale the node (3D object) it will only make bigger or smaller one part of the node and I want to scale everything.
I know one solution is to join the 3D object in Blender as one but, later on, I want to add specific animation to specific nodes and that's why I don't want to join them.
Here are the code that I'm using for scaling and moving the objects:
#objc func panned(recognizer :UIPanGestureRecognizer)
{
//This function is for scaling
if recognizer.state == .changed
{
guard let sceneView = recognizer.view as? ARSCNView
else
{
return
}
let touch = recognizer.location(in: sceneView)
let translation = recognizer.translation(in: sceneView)
let hitTestResults = self.sceneView.hitTest(touch, options: nil)
if let hitTest = hitTestResults.first
{
let planeNode = hitTest.node
self.newAngleY = Float(translation.x) * (Float) (Double.pi)/180
self.newAngleY += self.currentAngleY
planeNode.eulerAngles.y = self.newAngleY
}
else if recognizer.state == .ended
{
self.currentAngleY = self.newAngleY
}
}
}
And this one:
#objc func pinched(recognizer :UIPinchGestureRecognizer)
{
if recognizer.state == .changed
{
guard let sceneView = recognizer.view as? ARSCNView
else
{
return
}
}
let touch = recognizer.location(in: sceneView)
let hitTestResults = self.sceneView.hitTest(touch, options: nil)
if let hitTest = hitTestResults.first
{
let planeNode = hitTest.node
let pinchScaleX = Float(recognizer.scale) * planeNode.scale.x
let pinchScaleY = Float(recognizer.scale) * planeNode.scale.y
let pinchScaleZ = Float(recognizer.scale) * planeNode.scale.z
planeNode.scale = SCNVector3(pinchScaleX, pinchScaleY, pinchScaleZ)
recognizer.scale = 1
}
}
I don't know if this helps, but here is an image of the nodes:
Image of the nodes
Thanks in advance!
In order to achieve the "grouping" you are looking for you have to create an empty scene node that will be the root to all other nodes.
let myRootNode : SCNNode = SCNNode()
sceneView.scene.rootNode.addChildNode(myRootNode)
Next append all new nodes in the scene to myRootNode so all your models are parented to the same node.
let newChildNode : SCNNode = SCNNode()
myRootNode.addChildNode(newChildNode)
Then you can apply translations to the myRootNode to move it along with all child nodes. Also the child nodes remain separate models that can be moved (translated/rotated) individually or as a single group.
Within your hit test you should be scaling and rotating the myRootNode instead of the individual model.

How to swipe/slide go into the 3D Object

I have done small demo in ARKit. i am facing problem with how to go inside 3D object by swiping.
For example, i have home 3D object, its working with Tap Gesture, Rotation Gesture, i would like to go inside home by swiping one to another room inside home 3D object.
Its rotating whole 3D object node, i could not able to swipe and go inside home..
This is the code i used for rotate based Y axis,
#objc private func viewRotated(_ gesture: UIRotationGestureRecognizer) {
let location = gesture.location(in: sceneView)
guard let node = node(at: location) else { return }
switch gesture.state {
case .began:
originalRotation = node.eulerAngles
case .changed:
guard var originalRotation = originalRotation else { return }
originalRotation.y -= Float(gesture.rotation)
node.eulerAngles = originalRotation
default:
originalRotation = nil
}
}
To rotate in all direction using UIPanGestureRecognizer, this is the code i have added,
#objc func viewPanned(gestureRecognize: UIPanGestureRecognizer){
let translation = gestureRecognize.translation(in: gestureRecognize.view!)
let x = Float(translation.x)
let y = Float(-translation.y)
let anglePan = sqrt(pow(x,2)+pow(y,2))*(Float)(M_PI)/180.0
var rotationVector = SCNVector4()
rotationVector.x = -y
rotationVector.y = x
rotationVector.z = 0
rotationVector.w = anglePan
homeNode?.rotation = rotationVector
if(gestureRecognize.state == UIGestureRecognizerState.ended) {
//
let currentPivot = homeNode?.pivot
let changePivot = SCNMatrix4Invert( (homeNode?.transform)!)
homeNode?.pivot = SCNMatrix4Mult(changePivot, currentPivot!)
homeNode?.transform = SCNMatrix4Identity
}
}
Perhaps there is another way of doing this, Can someone suggest a way..Thanks..

ios - ARKit - How to change object position to another surface

I'm working with ARKit, I have a function to move the object like this:
var currentNode = SCNNode()
#objc func moveNode(_ gesture: UIPanGestureRecognizer) {
if !isRotating{
//1. Get The Current Touch Point
let currentTouchPoint = gesture.location(in: self.sceneView)
if gesture.state == .began {
guard let nodeHitTest = self.sceneView.hitTest(currentTouchPoint, options: nil).first else {return}
let nodeHit = nodeHitTest.node
currentNode = nodeHit
}
if gesture.state == .changed {
//2. Get The Next Feature Point Etc
guard let hitTest = self.sceneView.hitTest(currentTouchPoint, types: .existingPlane).first else { return }
//3. Convert To World Coordinates
let worldTransform = hitTest.worldTransform
//4. Set The New Position
let newPosition = SCNVector3(worldTransform.columns.3.x, worldTransform.columns.3.y, worldTransform.columns.3.z)
currentNode.simdPosition = float3(newPosition.x, newPosition.y, newPosition.z)
}
}
}
If my camera detect two surface, how can I move the object from current surface to another surface? Thanks all.

Using UIPanGesture to move node in ARScene

so I'm trying to set up a pan gesture to move a node within a sceneview to another area of the view. For example, if I place a cup on a flat surface I want the ability to be able to move the cup to another location on that surface. I have this so far from looking at other questions, but was still confused.
#objc func panGesture(sender: UIPanGestureRecognizer){
let sceneView = sender.view as! ARSCNView
let pannedLocation = sender.location(in: sceneView)
let hitTest = sceneView.hitTest(pannedLocation)
let state = sender.state
if(state == .failed || state == .cancelled){
return
}
if(state == .began){
let sceneHitTestResult = hitTest.first!
}
}
check out this answer for a full playground example Drag Object in 3D View
you need to add this line to viewDidLoad
sceneView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:))))
The basic panGesture function is...
#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
}
}

Resources