You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

  1. Track Touch Position: Add a variable to store the 3D world position corresponding to the user's touch.
  2. Convert Touch to 3D Space: Use hitTest to 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).
  3. 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 viewDidLoad to 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.5 value in the fallback logic.
  • Continuous Touch Updates: touchesMoved updates the 3D position in real-time as the user drags their finger, and touchesEnded resets the position when they lift their finger.

内容的提问来源于stack exchange,提问作者Charlie Lomello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:04:09