为什么SCNNode在放入SCNPlane时“摇摇晃晃”?

时间:2018-01-18 19:41:37

标签: ios swift scenekit arkit scnnode

当检测到水平表面有足够的区域时,我有一个SCNPlane添加到场景中。根据放置的地板/桌子,飞机似乎被放置在正确的位置。问题是,当我放弃一个SCNNode(这一直是一致的,无论是盒子,金字塔,3D模型等)到飞机上时,它最终会找到一个降落的地方,99%开始疯狂地摇摆。很少次它只是落地而根本没有移动。我也认为这可能是由于节点掉落并稍微落在平面下方引起的。它不是“在顶部”也不是“在飞机下方”。也许节点吓坏了,因为它在两个级别之间摇摇欲坠?

这是一个正在发生的事情的视频,你可以在开始时看到盒子位于飞机的下方和上方,橙色盒子在与深蓝色盒子碰撞时停止,但确实会回到它的摇摆方式当绿框在结尾碰撞时:

box jiggle

代码位于github

我还将展示代码中嵌入的一些相关部分:

我只需创建一个Plane类,以便在需要

时添加到场景中
class Plane: SCNNode {
var anchor :ARPlaneAnchor
var planeGeometry :SCNPlane!

init(anchor :ARPlaneAnchor) {
    self.anchor = anchor
    super.init()
    setup()
}

func update(anchor: ARPlaneAnchor) {
    self.planeGeometry.width = CGFloat(anchor.extent.x)
    self.planeGeometry.height = CGFloat(anchor.extent.z)
    self.position = SCNVector3Make(anchor.center.x, 0, anchor.center.z)
    let planeNode = self.childNodes.first!
    planeNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: self.planeGeometry, options: nil))
}

private func setup() {
    //plane dimensions
    self.planeGeometry = SCNPlane(width: CGFloat(self.anchor.extent.x), height: CGFloat(self.anchor.extent.z))
    //plane material
    let material = SCNMaterial()
    material.diffuse.contents = UIImage(named: "tronGrid.png")
    self.planeGeometry.materials = [material]
    //plane geometry and physics
    let planeNode = SCNNode(geometry: self.planeGeometry)
    planeNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: self.planeGeometry, options: nil))
    planeNode.physicsBody?.categoryBitMask = BodyType.plane.rawValue
    planeNode.position = SCNVector3Make(anchor.center.x, 0, anchor.center.z)
    planeNode.transform = SCNMatrix4MakeRotation(Float(-Double.pi / 2.0), 1, 0, 0)
    //add plane node
    self.addChildNode(planeNode)
}

这是ViewController

enum BodyType: Int {
    case box = 1
    case pyramid = 2
    case plane = 3
}
class ViewController: UIViewController, ARSCNViewDelegate, SCNPhysicsContactDelegate {
    //outlets
    @IBOutlet var sceneView: ARSCNView!
    //globals
    var planes = [Plane]()
    var boxes = [SCNNode]()
    //life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        //set sceneView's frame
        self.sceneView = ARSCNView(frame: self.view.frame)
        //add debugging option for sceneView (show x, y , z coords)
        self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
        //give lighting to the scene
        self.sceneView.autoenablesDefaultLighting = true
        //add subview to scene
        self.view.addSubview(self.sceneView)
        // Set the view's delegate
        sceneView.delegate = self
        //subscribe to physics contact delegate
        self.sceneView.scene.physicsWorld.contactDelegate = self
        //show statistics such as fps and timing information
        sceneView.showsStatistics = true
        //create new scene
        let scene = SCNScene()
        //set scene to view
        sceneView.scene = scene
        //setup recognizer to add scooter to scene
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
        sceneView.addGestureRecognizer(tapGestureRecognizer)
    }
    //MARK: helper funcs        
    @objc func tapped(recognizer: UIGestureRecognizer) {
        let scnView = recognizer.view as! ARSCNView
        let touchLocation = recognizer.location(in: scnView)
        let touch = scnView.hitTest(touchLocation, types: .existingPlaneUsingExtent)
        //take action if user touches box
        if !touch.isEmpty {
            guard let hitResult = touch.first else { return }
            addBox(hitResult: hitResult)
        }
    }

    private func addBox(hitResult: ARHitTestResult) {
        let boxGeometry = SCNBox(width:  0.1,
                                 height: 0.1,
                                 length: 0.1,
                                 chamferRadius: 0)
        let material = SCNMaterial()
        material.diffuse.contents = UIColor(red:    .random(),
                                            green:  .random(),
                                            blue:   .random(),
                                            alpha:  1.0)
        boxGeometry.materials = [material]
        let boxNode = SCNNode(geometry: boxGeometry)
        //adding physics body, a box already has a shape, so nil is fine
        boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
        //set bitMask on boxNode, enabling objects with diff categoryBitMasks to collide w/ each other
        boxNode.physicsBody?.categoryBitMask = BodyType.plane.rawValue | BodyType.box.rawValue
        boxNode.position = SCNVector3(hitResult.worldTransform.columns.3.x,
                                      hitResult.worldTransform.columns.3.y + 0.3,
                                      hitResult.worldTransform.columns.3.z)
        self.sceneView.scene.rootNode.addChildNode(boxNode)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = .horizontal
        //track objects in ARWorld and start session
        sceneView.session.run(configuration)
    }
    //MARK: - ARSCNViewDelegate
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        //if no anchor found, don't render anything!
        if !(anchor is ARPlaneAnchor) {
            return
        }
        DispatchQueue.main.async {
            //add plane to scene
            let plane = Plane(anchor: anchor as! ARPlaneAnchor)
            self.planes.append(plane)
            node.addChildNode(plane)
            //add initial scene object
            let pyramidGeometry = SCNPyramid(width: CGFloat(plane.planeGeometry.width / 8), height: plane.planeGeometry.height / 8, length: plane.planeGeometry.height / 8)
            pyramidGeometry.firstMaterial?.diffuse.contents = UIColor.white
            let pyramidNode = SCNNode(geometry: pyramidGeometry)
            pyramidNode.name = "pyramid"
            pyramidNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
            pyramidNode.physicsBody?.categoryBitMask = BodyType.pyramid.rawValue | BodyType.plane.rawValue
            pyramidNode.physicsBody?.contactTestBitMask = BodyType.box.rawValue
            pyramidNode.position = SCNVector3(-(plane.planeGeometry.width) / 3, 0, plane.planeGeometry.height / 3)
            node.addChildNode(pyramidNode)
        }
    }
    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        let plane = self.planes.filter {
            plane in return plane.anchor.identifier == anchor.identifier
        }.first

        if plane == nil {
            return
        }

        plane?.update(anchor: anchor as! ARPlaneAnchor)
    }
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        //pause session
        sceneView.session.pause()
    }
}

3 个答案:

答案 0 :(得分:2)

我想我遵循了相同的教程。我也有同样的结果。原因是因为当立方体从较高的位置掉落时,它会加速并且不会精确地击中飞机但是会通过。如果您将立方体缩小到“1毫米”。你可以看到盒子完全穿过飞机并继续下降到飞机下方。你可以尝试从靠近飞机的地方掉落立方体,箱子下降得更慢,而且这种“摇晃”不会发生。或者你可以尝试使用小高度而不是平面的盒子。

答案 1 :(得分:0)

“抖动”可能是由不正确的重力矢量引起的。尝试尝试设置场景的重力。

例如,将其添加到您的viewDidLoad函数中:

s.getVisit()

我发现通过代码或加载空场景来设置重力可以解决此问题。

答案 2 :(得分:0)

我遇到了同样的问题,发现了一个解决方案。我正在初始化ARSCNView programmatically。我只是删除了这些代码,并在ARSCNView中添加了storyboard使用IBOutlet将它加入到我的UIViewcontroller类中,它的工作原理就像是一种魅力。

希望它可以帮助遇到此问题的任何人。 下面是相同的代码。

@IBOutlet var sceneView: ARSCNView!

override func viewDidLoad() {
    super.viewDidLoad()
    self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints,ARSCNDebugOptions.showWorldOrigin]
    sceneView.delegate = self
    sceneView.showsStatistics = true
    let scene = SCNScene()
    sceneView.scene = scene

}