swift - ARKit - 如何使用 SceneKit 选择特定节点

标签 swift scenekit arkit

我是 arkit 框架的新手,但有一个小问题。我有这个项目,我可以使用对象 .scn,将其放置在平面上,移动、旋转和缩放。我的问题是:如果我放置一个对象,我可以处理它,但是当我放置第二个对象时,不可能移动第一个对象。所以现在我只能在最后一个节点上做一些事情,但我想使用所有这些节点。

代码 View Controller


import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {

    //=================== VARIABLES ========================//

    //is used to take memory about the selection that user do
    var selectedNode: SCNNode?

    //collection of node for deleting function
    var placedNodes = [SCNNode]()
    var planeNodes = [SCNNode]()

    //Point where is the last node
    var lastObjectPlacedPoint: CGPoint?

    @IBOutlet var sceneView: ARSCNView!
    let configuration = ARWorldTrackingConfiguration()

    //selections for the segmented control
    enum ObjectPlacementMode {
        case noselection, plane
    }

    //this variable of ObjectPlacementMode type, represents the 3 selections in the segmented control. Default is .freeform
    var objectMode: ObjectPlacementMode = .noselection{
        didSet{
            reloadConfiguration(removeAnchor: false)
        }
    }

    //variable to hide the plane
    var showPlanOverlay = false{
        didSet{
            for node in planeNodes{
            node.isHidden = !showPlanOverlay
        }
    }
    }

    //================== VIEW LIFECYCLE ======================//

    override func viewDidLoad() {
        super.viewDidLoad()

        sceneView.delegate = self
        sceneView.autoenablesDefaultLighting = true

        registerGestureRecognizers()

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        reloadConfiguration()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        sceneView.session.pause()
    }

    // ================== PREPARE FOR SEGUE ===================//

    //prepare for segue to pass the selected option to the optionController
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showOptions" {
            let optionsViewController = segue.destination as! OptionsContainerViewController
            optionsViewController.delegate = self
        }
    }

    // ================== IBACTION FUNCTION ===================//

    //this update the variable whe you click
    @IBAction func changeObjectMode(_ sender: UISegmentedControl) {
        switch sender.selectedSegmentIndex {
        case 0:
            objectMode = .noselection
        case 1:
            objectMode = .plane
        default:
            break
        }
    }

    // ================== TOUCH FUNCTION =====================//

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        //recall parent implementation
        super.touchesBegan(touches, with: event)

        //to verify that the user has chosen an object from the popover i check if the selectedNode has a value and if there is at least one finger touch
        guard let node = selectedNode,
              let touch = touches.first else {return}

        switch objectMode {

        //if the user select freeform, i pass the node to the method
        case .noselection:
 //           let touchPoint = touch.location(in: sceneView)
            break
        case .plane:
            let touchPoint = touch.location(in: sceneView)
            addNode(node, toPlaneUsingPoint: touchPoint)

        }
    }

    //when the user lifts the finger from the screen
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)

        lastObjectPlacedPoint = nil
    }

    // ===================== GESTURE RECOGNIZER WITH FUNCTION (MOVE, PINCH AND ROTATE) ======================= //

    private func registerGestureRecognizers(){
        let moveGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(move))
        moveGestureRecognizer.minimumPressDuration = 0.1
        self.sceneView.addGestureRecognizer(moveGestureRecognizer)

        let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(scale))
        self.sceneView.addGestureRecognizer(pinchGestureRecognizer)

        let rotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(rotate))
        self.sceneView.addGestureRecognizer(rotationGestureRecognizer)
    }

    @objc func move(recognizer: UILongPressGestureRecognizer){
        guard let scene = recognizer.view as? ARSCNView else {return}
        let touch = recognizer.location(in: scene)
        let results = sceneView.hitTest(touch, types: [.existingPlaneUsingExtent])

        if let match = results.first {
            let t = match.worldTransform
            placedNodes.last?.position = SCNVector3(x: t.columns.3.x, y: t.columns.3.y, z: t.columns.3.z)
        }
    }

    @objc func scale(sender: UIPinchGestureRecognizer) {
        let sceneView = sender.view as! ARSCNView
        let pinchLocation = sender.location(in: sceneView)
        let hitTest = sceneView.hitTest(pinchLocation)

        if !hitTest.isEmpty {

            let results = hitTest.first!
            let node = results.node
            let pinchAction = SCNAction.scale(by: sender.scale, duration: 0)
            print(sender.scale)
//          node.runAction(pinchAction)
            placedNodes.last?.runAction(pinchAction)
            sender.scale = 1.0
        }

    }

    @objc func rotate(sender: UIRotationGestureRecognizer) {
        let sceneView = sender.view as! ARSCNView
        let holdLocation = sender.location(in: sceneView)
        let hitTest = sceneView.hitTest(holdLocation)

        if !hitTest.isEmpty {

            let result = hitTest.first!

            if sender.state == .began {
                let rotation = SCNAction.rotateBy(x: 0, y: CGFloat(360.degreesToRadians), z: 0, duration: 1)
//                let forever = SCNAction.repeatForever(rotation)
//                result.node.runAction(rotation)
                placedNodes.last?.runAction(rotation)
            } else if sender.state == .ended {
                result.node.removeAllActions()
            }
        }
    }

    // ==================== DETECT PLANE, CREATE FLOOR AND ADD NODE TO PLAN DETECTED ========================= //

    //with this method you can recognize image or plane, in order to add a node on it
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

        if let planeAnchor = anchor as? ARPlaneAnchor{

            nodeAdded(node, for: planeAnchor)

        }
    }

    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let planeAnchor = anchor as? ARPlaneAnchor,
              let planeNode = node.childNodes.first,
              let plane = planeNode.geometry as? SCNPlane else{ return }

        //update the dimension of the plane
        planeNode.position = SCNVector3(planeAnchor.center.x, 0, planeAnchor.center.z)
        plane.width = CGFloat(planeAnchor.extent.x)
        plane.height = CGFloat(planeAnchor.extent.z)
    }

    func createFloor(planeAnchor: ARPlaneAnchor) -> SCNNode {

        let node = SCNNode()

        //here i'm creating a plane with size that match the dimension of the planeAnchor
        let geometry = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z))

        node.geometry = geometry

        //Rotation of the plane 90 degree
        node.eulerAngles.x = -Float.pi / 2
        node.opacity = 0.25

        return node
    }

    // ======================== ADD NODE FUNCTION ========================= //

    //when you tap, will be created a clone node that will be added to the scene and in the list
    func addNodeToSceneRoot(_ node:SCNNode){
        let cloneNode = node.clone()
        sceneView.scene.rootNode.addChildNode(cloneNode)
        placedNodes.append(cloneNode)
    }

    //when you detect a plane, create a node. From now every other node will be children of this, not to the root node. Control that the object must be on a plane
    func addNode(_ node:SCNNode, toPlaneUsingPoint point: CGPoint){

        let results = sceneView.hitTest(point, types: [.existingPlaneUsingExtent])

        if let match = results.first {

            let t = match.worldTransform
            node.position = SCNVector3(x: t.columns.3.x, y: t.columns.3.y, z: t.columns.3.z)
            addNodeToSceneRoot(node)

            lastObjectPlacedPoint = point
        }
    }

    func nodeAdded(_ node: SCNNode, for anchor: ARPlaneAnchor){
        let floor = createFloor(planeAnchor: anchor)
        floor.isHidden = !showPlanOverlay

        //add node (plane) to the scene, and add it to the array
        node.addChildNode(floor)
        planeNodes.append(floor)
    }

    // ======================== RELOAD CONFIGURATION ============================ //

    //this method reload the session based on the value of the objectMode, with plane detection always active
    func reloadConfiguration(removeAnchor: Bool = true){

        configuration.planeDetection = [.horizontal, .vertical]

        let options: ARSession.RunOptions

        if removeAnchor {
            options = [.removeExistingAnchors]

            for node in planeNodes{
                node.removeFromParentNode()
            }

            planeNodes.removeAll()

            for node in placedNodes{
                node.removeFromParentNode()
            }
            placedNodes.removeAll()

        }else{
            options = []
        }

        sceneView.session.run(configuration, options: options)
    }

}

// =========================== EXTENSION ============================= //

//this list of method will be called whe you select an option in the Options menu
extension ViewController: OptionsViewControllerDelegate {

    //this one is called whe the user has selected the shape, color, size
    func objectSelected(node: SCNNode) {
        dismiss(animated: true, completion: nil)
        selectedNode = node
    }

    //this is called when the user tap on Enable/Disable Plane Visualization
    func togglePlaneVisualization() {
        dismiss(animated: true, completion: nil)
        showPlanOverlay = !showPlanOverlay
    }

    //this is called when the user tap on Undo Last Object, and locate the last node, remove it from the scene and remove it from the collection
    func undoLastObject() {
        if let lastNode = placedNodes.last{
            lastNode.removeFromParentNode()
            placedNodes.removeLast()
        }
    }

    //this is called when the user tap on Reset Scene
    func resetScene() {
        dismiss(animated: true, completion: nil)
        reloadConfiguration()
    }
}

extension Int {

    var degreesToRadians: Double { return Double(self) * .pi/180}
}


感谢大家对我的帮助。

最佳答案

您的手势函数正在修改 placedNodes.last 而不是 selectedNode

关于swift - ARKit - 如何使用 SceneKit 选择特定节点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59239015/

相关文章:

ios - 删除 bridgeToObjective-C 意味着我不能使用 NSRange

ios - 状态栏中的事件指示器始终隐藏

ios - 根据日期对对象数组进行排序

swift - 在 SceneKit 中使用 Metal 着色器

augmented-reality - ARKit 和 Vision 框架——检测墙壁边缘

ios - 如何使用 ARKit 将 SCNScene 与物理表对齐?

swift - ARKit – 将 OBJ 或 DAE 转换为 arobject

swift - 如何更改 "i"圆圈颜色(标题颜色)

swift - 为什么 SceneKit 中没有运算符重载?

ios - 无法在 SK3DNode 上方显示 Sprite