4

I have a SCNPlane that is added to the scene when a sufficient area is detected for a horizontal surface. The plane appears to be placed in a correct spot, according to the floor/table it's being placed on. The problem is when I drop a SCNNode(this has been consistent whether it was a box, pyramid, 3D-model, etc.) onto the plane, it will eventually find a spot to land and 99% start jiggling all crazy. Very few times has it just landed and not moved at all. I also think this may be cause by the node being dropped and landing slightly below the plane surface. It is not "on top" neither "below" the plane. Maybe the node is freaking out because it's kind of teetering between both levels?

Here is a video of what's going on, you can see at the beginning that the box is below and above the plane and the orange box does stop when it collides with the dark blue box, but does go back to its jiggling ways when the green box collides with it at the end:

box jiggle

The code is here on github

I will also show some of the relevant parts embedded in code:

I just create a Plane class to add to the scene when I need to

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

This is the 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()
    }
}
Jake Dobson
  • 1,716
  • 3
  • 11
  • 29
  • Having the same problem. Did you find a solution? – Nailer Apr 05 '18 at 10:55
  • not quite...the answer below did help, but seems weird to have to use a box just for the reason a plane should exist. To clarify, using a box with a height of like .001(or as low as possible) was what prevented the jiggling seen above. – Jake Dobson Apr 05 '18 at 22:37
  • I think there might be an issue with the collision mesh of my dynamic object. Dropping a SCNBox onto the plane did not result in any jiggling. – Nailer Apr 09 '18 at 08:11
  • collision mesh? What is that and how do you change it? Were you having the same problem as me and now you are not? – Jake Dobson Apr 09 '18 at 15:29
  • I created a SCNPhysicsShape with a box in the SCNPhysicsBody assigned to the SCNNode. However, after some more research, I'm not so sure that just using a box works. – Nailer Apr 10 '18 at 12:11

3 Answers3

2

I think i followed the same tutorial. I also had same result. Reason is because when the cube drops from higher place, it accelerates and doesnot exactly hit on the plane but passes through. If you scale down the cube to '1 mm' you can see box completely passes through plane and continue falling below plane.
You can try droping cube from nearer to the plane, box drops slower and this 'jiggling' will not occur. Or you can try with box with small height instead of plane.

Alok Subedi
  • 1,601
  • 14
  • 26
  • 1
    yeah, I've noticed if the node is very small, it will just slip right through. Seems to defeat the purpose of a plane, if things don't land on top of it....When it works fine as a box. – Jake Dobson Jan 22 '18 at 15:30
  • Replacing the SCNPlane with an SCNBox with height 0.001 fix the issue for me – SilentK Sep 25 '18 at 10:49
0

The "jiggling" is probably caused by an incorrect gravity vector. Try experimenting with setting the gravity of your scene.

For example, add this to your viewDidLoad function:

sceneView.scene.physicsWorld.gravity = SCNVector3Make(0.0, -1.0, 0.0)

I found that setting the gravity - either through code, or by loading an empty scene - resolves this issue.

bloodopal
  • 154
  • 1
  • 9
0

I had the same problem i found out one solution.I was initializing the ARSCNView programmatically.I just removed those code and just added a ARSCNView in the storyboard joined it in my UIViewcontroller class using IBOutlet it worked like a charm.

Hope it helps anyone who is going through this problem. The same code is below.

@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

}
  • would be weird if it did...maybe because there was actually 2 views or something by adding in code? – Jake Dobson Apr 21 '19 at 01:00
  • Some suggested me to use gravity but it was not working i will update my previous answer with sample code that i used .If any confusion please let me know i will try to help as much as I can – Robert Shrestha Apr 21 '19 at 13:47