I'm creating an app that choose image from gallery and place it on wall using ARKit vertical plane detection.I'm also able to scale it properly but dragging is not as expected. Also, i'm also confused about image size. I want it like suppose if wall is of 10 feet and i want to place image of 3*2 feet.I referred many links and docs.Below is the code that renders and add place image:
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for
anchor: ARAnchor) {
if nodeWeCanChange == nil{
guard let planeAnchor = anchor as? ARPlaneAnchor else { return
}
let width = CGFloat(planeAnchor.extent.x)
let height = CGFloat(planeAnchor.extent.z)
nodeWeCanChange = SCNNode(geometry: SCNPlane(width: width,
height: height))
nodeWeCanChange?.position = SCNVector3(planeAnchor.center.x, 0,
planeAnchor.center.z)
nodeWeCanChange?.transform = SCNMatrix4MakeRotation(-Float.pi / 2.0, 1.0, 0.0, 0.0)
nodeWeCanChange?.geometry?.firstMaterial?.diffuse.contents = UIColor.white
DispatchQueue.main.async(execute: {
node.addChildNode(self.nodeWeCanChange!)
self.zIndex = (self.nodeWeCanChange?.simdPosition.z)!})
} }
}
func placeImage(image : UIImage){
guard let pointsPerInch = UIScreen.pointsPerInch else{
return}
let pixelWidth = (image.size.width) * (image.scale)
let pixelHeight = (image.size.height) * (image.scale)
let inchWidth = pixelWidth/pointsPerInch
let inchHeight = pixelHeight/pointsPerInch
let widthInMetres = (inchWidth * 2.54) / 100
let heightInMeters = (inchHeight * 2.54) / 100
let photo = SCNPlane(width: (widthInMetres), height: (heightInMeters))
nodeWeCanChange?.geometry = photo
nodeWeCanChange?.geometry?.firstMaterial?.diffuse.contents = image}`
@objc func handleTap(_ gestureRecognize: ThresholdPanGesture)
{
// Function that handles drag
let position = gestureRecognize.location(in: augentedRealityView)
var foundNode:SCNNode? = nil
do {
if gestureRecognize.state == .began {
print("Pan state began")
//let hitTestOptions = [SCNHitTestOption: Any]()
let hitResult: [SCNHitTestResult] = (self.augentedRealityView?.hitTest(gestureRecognize.location(in: self.augentedRealityView), options: nil))!
guard let firstNode = hitResult.first else {
return
}
print("first node =\(firstNode.node)")
if firstNode.node.isEqual(nodeWeCanChange){
foundNode = nodeWeCanChange
print("node found")}
else if (firstNode.node.parent?.isEqual(nodeWeCanChange))!{ print("node found")}
else{ return print("node not found")}
latestTranslatePos = position
}
}
if gestureRecognize.state == .changed{
let deltaX = Float(position.x - latestTranslatePos.x)/700
let deltaY = Float(position.y - latestTranslatePos.y)/700
nodeWeCanChange?.localTranslate(by: SCNVector3Make(deltaX, -deltaY , zIndex))
latestTranslatePos = position
print("Pan State Changed")
}
if gestureRecognize.state == .ended {
print("Done moving object")
let deltaX = Float(position.x - latestTranslatePos.x)/700
let deltaY = Float(position.y - latestTranslatePos.y)/700
nodeWeCanChange?.localTranslate(by: SCNVector3Make(deltaX, -deltaY , zIndex))
latestTranslatePos = position
foundNode = nil
}
}`
Can any one please help me what i'm missing?
There are two questions here... ^__________^.
In regard to panning your SCNNode
the easiest thing to do would be to use touches
, although you can modify the example below quite easily.
Assuming your content is placed on ARPlaneAnchor
and you want to move it on the plane then you can do something like this (assuming you have also selected an SCNNode
to be your nodeToDrag
) which in my example will be the picture:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//1. Get The Current Touch Point
guard let currentTouchPoint = touches.first?.location(in: self.augmentedRealityView),
//2. Get The Existing ARPlaneAnchor
let hitTest = augmentedRealityView.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)
//5. Apply To The Node
nodeToDrag.simdPosition = float3(newPosition.x, newPosition.y, newPosition.z)
}
Alternatively you can try the following:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//1. Get The Current Touch Point
guard let currentTouchPoint = touches.first?.location(in: self.augmentedRealityView),
//2. Get The Existing ARPlaneAnchor
let hitTest = augmentedRealityView.hitTest(currentTouchPoint, types: .existingPlane).first else { return }
//3. Convert To Local Coordinates
nodeToDrag.simdPosition.x = Float(hitTest.localTransform.columns.3.x)
nodeToDrag.simdPosition.y = Float(-hitTest.localTransform.columns.3.z)
}
In regard to the sizing you it's not entirely clear what you are wanting to do. However here is a small example with comments that should help:
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
//1. Check We Have An ARPlaneAnchor
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
//2. Get The Approximate Width & Height Of The Anchor
let width = CGFloat(planeAnchor.extent.x)
let height = CGFloat(planeAnchor.extent.z)
//3. Create A Dummy Wall To Illustrate The Vertical Plane
let dummyWall = SCNNode(geometry: SCNPlane(width: width, height: height))
dummyWall.geometry?.firstMaterial?.diffuse.contents = UIColor.black
dummyWall.opacity = 0.5
dummyWall.eulerAngles.x = -.pi/2
node.addChildNode(dummyWall)
//4. Create A Picture Which Will Be Half The Size Of The Wall
let picture = SCNNode(geometry: SCNPlane(width: width/2, height: height/2))
picture.geometry?.firstMaterial?.diffuse.contents = UIColor.white
//5. Add It To The Dummy Wall (As We Dont Assign A Position It Will Be Centered Automatically At SCNVector3Zero)
dummyWall.addChildNode(picture)
//6. To Prevent Z-Fighting Move The Picture Forward Slightly
picture.position.z = 0.0001
}
You could also create a helper function e.g:
//--------------------------
//MARK: - CGFloat Extensions
//--------------------------
extension CGFloat{
/// Desired Scale Factor
enum Scalar{
case Quarter
case Third
case Half
case ThreeQuarters
case FullSize
var value: CGFloat{
switch self {
case .Quarter: return 0.25
case .Third: return 0.33
case .Half: return 0.5
case .ThreeQuarters: return 0.75
case .FullSize: return 1
}
}
}
/// Returns A CGFloat Based On The Desired Scale
///
/// - Parameter size: Scalar
/// - Returns: CGFloat
func scaledToSize(_ size: Scalar) -> CGFloat {
return self * size.value
}
}
Which you can use to scale your picture based on the extent of the anchor e.g:
let width = CGFloat(planeAnchor.extent.x)
let height = CGFloat(planeAnchor.extent.z)
let picture = SCNNode(geometry: SCNPlane(width: width.scaledToSize(.ThreeQuarters), height: height.scaledToSize(.ThreeQuarters)))
Update:
Since you were unable to get the example to work, and have now been more specific as to you requirements here is a fully working example (which needs a little tweaking) but it should be more than enough to point you in the right direction:
//-----------------------
//MARK: ARSCNViewDelegate
//-----------------------
extension ARViewController: ARSCNViewDelegate{
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if wallAndPictureNode != nil { return }
//1. Check We Have An ARPlaneAnchor
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
//2. Get The Approximate Width & Height Of The Anchor
let width = CGFloat(planeAnchor.extent.x)
let height = CGFloat(planeAnchor.extent.z)
//3. Create A Dummy Wall To Illustrate The Vertical Plane
let dummyWall = SCNNode(geometry: SCNPlane(width: width, height: height))
dummyWall.geometry?.firstMaterial?.diffuse.contents = UIColor.black
dummyWall.opacity = 0.5
dummyWall.eulerAngles.x = -.pi/2
node.addChildNode(dummyWall)
//4. Create A Picture Which Will Be Half The Size Of The Wall
let picture = SCNNode(geometry: SCNPlane(width: width/2, height: height/2))
picture.geometry?.firstMaterial?.diffuse.contents = UIColor.white
//5. Add It To The Dummy Wall (As We Dont Assign A Position It Will Be Centered Automatically At SCNVector3Zero)
dummyWall.addChildNode(picture)
//6. To Prevent Z-Fighting Move The Picture Forward Slightly
picture.position.z = 0.0001
//7. Set The Wall & Picture Node
wallAndPictureNode = dummyWall
}
}
class ARViewController: UIViewController {
//1. Create A Reference To Our ARSCNView In Our Storyboard Which Displays The Camera Feed
@IBOutlet weak var augmentedRealityView: ARSCNView!
//2. Create Our ARWorld Tracking Configuration
let configuration = ARWorldTrackingConfiguration()
//4. Create Our Session
let augmentedRealitySession = ARSession()
//5. Create Variable To Store Our Wall With The Picture On It
var wallAndPictureNode: SCNNode?
//--------------------
//MARK: View LifeCycle
//--------------------
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) { setupARSession() }
override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() }
//-------------------
//MARK: - Interaction
//-------------------
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//1. Get The Current Touch Point, Check We Have Our WallAndPictureNode, & Then Check We Have A Valid ARPlaneAnchor
guard let nodeToMove = wallAndPictureNode,
let currentTouchPoint = touches.first?.location(in: self.augmentedRealityView),
let hitTest = augmentedRealityView.hitTest(currentTouchPoint, types: .existingPlane).first else { return }
//3. Move The Wall & Picture
nodeToMove .simdPosition.x = Float(hitTest.worldTransform.columns.3.x)
nodeToMove .simdPosition.z = Float(-hitTest.worldTransform.columns.3.y)
}
//---------------
//MARK: ARSession
//---------------
/// Sets Up The ARSession
func setupARSession(){
//1. Set The AR Session
augmentedRealityView.session = augmentedRealitySession
augmentedRealityView.delegate = self
//2. Conifgure The Type Of Plane Detection
configuration.planeDetection = [.vertical]
//3. Configure The Debug Options
augmentedRealityView.debugOptions = debug(.None)
//4. Run The Session
augmentedRealitySession.run(configuration, options: [.resetTracking, .removeExistingAnchors])
//5. Disable The IdleTimer
UIApplication.shared.isIdleTimerDisabled = true
}
}
Hope it helps...