ARKit hitTest(_:options:) to select placed 3d-objects not working

817 views Asked by At

I am trying to select an object which has been placed on a detected plane in order to perform various task on it such as rotating through gestures.

In order to search for placed objects and avoid getting hitTestResults of irrelevant nature (eg. selecting the plane or the ARWorldMap itself) I am trying to use hitTest(_:options:) with SCNHitTestOption.categoryBitMask. However it seems as the hitTest returns results of all types, not just objects with the selected categoryBitMask = 5, even though from my understanding categoryBitMask is "An option to search only for objects matching a specified bitmask." How do I fix this problem, and is there a better way to select placed 3D-models in ARKit? Below is the function I have to rotate a 3d-model.

enum BodyType: Int {
    case model = 5
}

@objc func panned(recognizer :UIPanGestureRecognizer) {
    guard let recognizerView = recognizer.view as? ARSCNView else {return}

    let touch = recognizer.location(in: recognizerView)
    let translation = recognizer.translation(in: recognizerView)

    let hitTestResult = self.sceneView.hitTest(touch, options: [SCNHitTestOption.categoryBitMask: BodyType.model.rawValue])
    guard let modelNodeHit = hitTestResult.first?.node.parent else{return}
    if recognizer.state == .changed {
            self.newAngleY = Float(translation.x) * (Float) (Double.pi) / 180
            self.newAngleY += self.currentAngleY
            modelNodeHit.eulerAngles.y = self.newAngleY
    }else if recognizer.state == .ended {
        self.currentAngleY = self.newAngleY
    }
}
1

There are 1 answers

1
jlsiewert On BEST ANSWER

is there a better way to select placed 3D-models in ARKit

No, you are right. Use SCNSceneRenderer.hitTest(_:, options:) when searching for SceneKit content and ARSCNView.hitTest(_:types:) when searching for real objects recognised by ARKit.

What seems wrong here is that categoryBitMask is, well, a bitmask. 5 has a binary representation of 101. SceneKit then compares every bit with the bits on your objects and if any of them match, it includes the object in the results.
So when every other object has the default category of 1, it is included in the result, because 101 and 001 have a matching bit.

What you can use is the OptionSet protocol.

struct BodyType: OptionSet {
  let rawValue: Int

  static let `default` = BodyType(rawValue: 1)
  static let userInteraction = BodyType(rawValue: 4)

  static let model: BodyType = [.default, .userInteraction]
}

Your model gets the model option, but in your hit-testing you only use .userInteraction.