Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Swift ARKit-如何使用SceneKit选择特定节点_Swift_Scenekit_Arkit - Fatal编程技术网

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

Swift ARKit-如何使用SceneKit选择特定节点,swift,scenekit,arkit,Swift,Scenekit,Arkit,我是arkit框架的新手,我有一个小问题。我有这个项目,我可以使用object.scn,把它放在一个平面上,移动,旋转和缩放。我的问题是:如果我放置一个物体,我可以处理它,但当我放置第二个物体时,就不可能移动第一个物体。所以现在我只能在最后一个节点上做一些事情,但我想和所有节点一起工作 代码视图控制器 import UIKit import SceneKit import ARKit class ViewController: UIViewController, ARSCNViewDeleg

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

代码视图控制器


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}
}



导入UIKit
导入SceneKit
进口阿基特
类ViewController:UIViewController、ARSCNViewDelegate{
//=========================变量========================//
//用于获取有关用户所做选择的内存
变量selectedNode:SCNNode?
//用于删除函数的节点集合
变量placedNodes=[SCNNode]()
变量planeNodes=[SCNNode]()
//最后一个节点在哪里
var lastObjectPlacedPoint:CGPoint?
@IBVAR场景视图:ARSCNView!
let configuration=ARWorldTrackingConfiguration()
//分段控件的选择
枚举ObjectPlacementMode{
病例选择,平面
}
//此ObjectPlacementMode类型的变量表示分段控件中的3个选择。默认值为.freeform
var objectMode:ObjectPlacementMode=.noselection{
迪塞特{
重新加载配置(removeAnchor:false)
}
}
//变量来隐藏平面
var showPlanOverlay=false{
迪塞特{
对于平面节点中的节点{
node.ishiden=!showPlanOverlay
}
}
}
//=========================查看生命周期======================//
重写func viewDidLoad(){
super.viewDidLoad()
sceneView.delegate=self
sceneView.autoenablesDefaultLighting=true
registerGestureRecognizers()
}
覆盖函数视图将出现(uo动画:Bool){
超级。视图将显示(动画)
重新加载配置()
}
覆盖函数视图将消失(u动画:Bool){
超级。视图将消失(动画)
sceneView.session.pause()
}
//====================================为赛段做准备===================//
//准备将所选选项传递给optionController
覆盖功能准备(对于segue:UIStoryboardSegue,发送方:有吗?){
如果segue.identifier==“showOptions”{
让optionViewController=segue.destination为!optionContainerServiceController
选项ViewController.delegate=self
}
}
//============================iAction函数===================//
//这将在单击时更新变量
@iAction func changeObjectMode(\发送方:UISegmentedControl){
切换发送器。选择段索引{
案例0:
objectMode=.noselection
案例1:
objectMode=.plane
违约:
打破
}
}
//=========================触摸功能=====================//
覆盖func TouchesBegind(Touchs:Set,带有事件:UIEvent?){
//召回父实现
super.touchesbeated(touches,with:event)
//为了验证用户是否从popover中选择了对象,我检查selectedNode是否有值,以及是否至少有一次手指触摸
guard let node=selectedNode,
让touch=touch.first-else{return}
切换对象模式{
//如果用户选择freeform,我将节点传递给该方法
案例.选择:
//让接触点=接触位置(在:场景视图中)
打破
案件.飞机:
让接触点=接触位置(在:场景视图中)
addNode(节点,toPlaneUsingPoint:接触点)
}
}
//当用户从屏幕上抬起手指时
覆盖函数touchesend(touchs:Set,带有事件:UIEvent?){
super.touchesend(触摸,带有:事件)
lastObjectPlacedPoint=nil
}
//========================================================================================================================//
专用func注册表检测识别器(){
让moveGestureRecognizer=ui长按GestureRecognizer(目标:自我,操作:#选择器(移动))
MoveGestureRecognitor.minimumPressDuration=0.1
self.sceneView.addgestureignizer(移动gestureignizer)
让pinchGestureRecognizer=UIPinchGestureRecognizer(目标:自我,动作:#选择器(比例))
self.sceneView.addgestureignizer(pinchgestureignizer)
让rotationGestureRecognizer=UIRotationGestureRecognizer(目标:自我,动作:#选择器(旋转))
self.sceneView.addgestureignizer(旋转gestureignizer)
}
@objc func move(识别器:UILongPressGestureRecognitor){
guard let scene=recognizer.view as?ARSCNView else{return}
let touch=识别器。位置(在:场景中)
让结果=sceneView.hitTest(触摸,类型:[.existingPlaneUsingExtent])
如果让match=results.first{
设t=match.worldTransform
placedNodes.last?.position=SCInvector3(x:t.columns.3.x,y:t.columns.3.y,z:t.columns.3.z)
}
}
@objc func量表(发送方:UIPinchGestureRecognitor){
让sceneView=sender.view为!ARSCNView
让pinchLocation=sender.location(在:sceneView中)
让hitTest=sceneView.hitTest(pinchLocation)
如果!hitTest.isEmpty{
让结果=hitTest.first!
让node=results.node
让pinchAction=SCNAction.scale(by:sender.scale,持续时间:0)
打印(发送器刻度)
//node.runAction(pinchAction)
placedNodes.last?运行操作(pinchAction)
sender.scale=1.0
}
}
@objc func rotate(发送方:UIRotationGestureRecognitor){
让sceneView=sender.view为!ARSCNView
让holdLocation=sender.location(在:sceneView中)
让hitTest=sceneView.hitTest(holdLocation)
如果!hitTest.isEmpty{
让结果=hitTest