Swift中如何通过手指拖动调整ARKit相机位置(3D绘图Demo)
Adjust ARKit Drawing Position to Touch Location
Great work on your ARKit 3D drawing demo! To make the drawing position follow where the user touches the screen, we need to convert 2D touch coordinates into a 3D world position using ARKit's hit-testing capabilities. Here's a step-by-step modification to your code:
Key Changes Explained
- Track Touch Position: Add a variable to store the 3D world position corresponding to the user's touch.
- Convert Touch to 3D Space: Use
hitTestto map the screen touch to a real-world position (either on a detected plane or a point in front of the camera if no plane is found). - Update Drawing Logic: Modify your renderer method to use the touch-derived position instead of the camera's center when the user is touching the screen.
Modified Full Code
import UIKit import ARKit class ViewController: UIViewController, ARSCNViewDelegate { @IBOutlet weak var sceneView: ARSCNView! @IBOutlet weak var DRAW: UIButton! @IBOutlet weak var DEL: UIButton! let config = ARWorldTrackingConfiguration() // Variable to store the 3D position from user touch private var touchWorldPosition: SCNVector3? override func viewDidLoad() { super.viewDidLoad() self.sceneView.session.run(config) self.sceneView.delegate = self // Enable horizontal plane detection for more accurate hit test results config.planeDetection = .horizontal } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let touchLocation = touch.location(in: sceneView) // Perform hit test to find real-world position matching the touch let hitTestResults = sceneView.hitTest(touchLocation, types: [.existingPlaneUsingExtent, .estimatedHorizontalPlane]) if let result = hitTestResults.first { // Extract world position from the hit test result's transform let worldTransform = result.worldTransform touchWorldPosition = SCNVector3( worldTransform.columns.3.x, worldTransform.columns.3.y, worldTransform.columns.3.z ) } else { // Fallback: if no plane is detected, use a point 0.5 meters in front of the camera guard let pointOfView = sceneView.pointOfView else { return } let transform = pointOfView.transform let cameraOrientation = SCNVector3(-transform.m31, -transform.m32, -transform.m33) let cameraLocation = SCNVector3(transform.m41, transform.m42, transform.m43) touchWorldPosition = cameraLocation + cameraOrientation * 0.5 } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { // Reset touch position when the user stops touching the screen touchWorldPosition = nil } func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) { guard let pointOfView = sceneView.pointOfView else {return} let transform = pointOfView.transform let cameraOrientation = SCNVector3(-transform.m31,-transform.m32,-transform.m33) let cameraLocation = SCNVector3(transform.m41,transform.m42,transform.m43) // Use touch-derived position if available; fall back to camera center otherwise let cameraCurrentPosition = touchWorldPosition ?? (cameraOrientation + cameraLocation) DispatchQueue.main.async { if (self.DRAW.isTouchInside){ let sphereNode = SCNNode(geometry: SCNSphere(radius: 0.02)) sphereNode.position = cameraCurrentPosition self.sceneView.scene.rootNode.addChildNode(sphereNode) sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red print("RED Button is Pressed") }else if (self.DEL.isTouchInside){ self.sceneView.scene.rootNode.enumerateChildNodes{ (node, stop) in node.removeFromParentNode() } }else{ let pointer = SCNNode(geometry: SCNSphere(radius: 0.01)) pointer.name = "pointer" pointer.position = cameraCurrentPosition self.sceneView.scene.rootNode.enumerateChildNodes({(node,_) in if node.name == "pointer"{ node.removeFromParentNode() } }) self.sceneView.scene.rootNode.addChildNode(pointer) pointer.geometry?.firstMaterial?.diffuse.contents = UIColor.purple } } } } // Helper operators for vector math func +(left:SCNVector3,right:SCNVector3) -> SCNVector3 { return SCNVector3Make(left.x + right.x, left.y + right.y, left.z + right.z) } func *(left: SCNVector3, right: Float) -> SCNVector3 { return SCNVector3Make(left.x * right, left.y * right, left.z * right) }
Additional Notes
- Plane Detection: I enabled horizontal plane detection in
viewDidLoadto make hit tests more accurate when the user touches flat surfaces like tables or floors. - Fallback Distance: The fallback position uses a point 0.5 meters in front of the camera—you can adjust this distance by changing the
0.5value in the fallback logic. - Continuous Touch Updates:
touchesMovedupdates the 3D position in real-time as the user drags their finger, andtouchesEndedresets the position when they lift their finger.
内容的提问来源于stack exchange,提问作者Charlie Lomello
相关产品推荐
相关产品推荐

