blue
blue

Reputation: 7375

Reality Composer - ar hittest not working?

Alright, Im weeding thru the provided RealityComposer game at https://developer.apple.com/videos/play/wwdc2019/609/ and am trying to figure out how to have an entity move where the user taps.

I can reference my objects in my .rc scene like this:

struct ARViewContainer: UIViewRepresentable {

    let arView = ARView(frame: .zero)

    func makeUIView(context: Context) -> ARView {

       // arView = ARView(frame: .zero)

        // Load the "Box" scene from the "Experience" Reality File
        let boxAnchor = try! Experience.loadBox()

        //*Notifications
        setupNotifications(anchor: boxAnchor)

        //*Access vars
        if boxAnchor.box1 != nil {
            //print(box1.position.x)
            /// Set local position
            boxAnchor.box1!.position = [1.0, 0.0, 0.5]
            /// Set world position
            //boxAnchor.box1.setPosition([0.5, 0.2, 1.5], relativeTo: nil)
        }

I know this involves some form arhitest, however with the following I get the error:

UIView has no member last?

func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

       //1. Get The Current Touch Location
        guard let currentTouchLocation = touches.first?.location(in: self.arView),

            //2. Get The Tapped Node From An SCNHitTest
            let hitTestResultNode = self.arView.hitTest(currentTouchLocation, with: nil).last?.node else { return } //error here

        //3. Loop Through The ChildNodes
        for node in hitTestResultNode.childNodes{

            //4. If The Node Has A Name Then Print It Out
            if let validName = node.name{
                 print("Node\(validName) Is A Child Node Of \(hitTestResultNode)")
            }

        }

    }

Im pretty lost as to whether Im going about this at all correctly. Im referencing Detect touch on SCNNode in ARKit but this does not deal with RealityComposer.

How can I do this?

Upvotes: 1

Views: 1049

Answers (1)

PongBongoSaurus
PongBongoSaurus

Reputation: 7385

The simplest way to get an Entity to move via touch is to use the built in gestures provides by Apple; which you can read more about here: Apple Documentation

To enable your gesture of choice (in this case translation), first ensure that in RealityComposer that you set the partcipates value to true on every Entity you wish to interact with.

enter image description here

This then adds a Has Collision component to the Entitywhich is simply:

A component that gives an entity the ability to collide with other entities that also have collision components.

Using this you can install built in gestures to do the heavy lifting for you.

Assuming we have the default RealityKit example setup in Xcode, and we have selected participates for the box, its a simple as this to enable the user to pan it using the touch location:

class ViewController: UIViewController {

    @IBOutlet var arView: ARView!

    override func viewDidLoad() {

      super.viewDidLoad()

        //1. Load The Box Anchor
        let boxAnchor = try! Experience.loadBox()

        //2. Check The SteelBox Has A Collision Component & Add The Desired Gesture
        if let hasCollision = boxAnchor.steelBox as? HasCollision {
          arView.installGestures(.translation, for: hasCollision)
       }

       //Add The Box To The Scene Hierachy
       arView.scene.anchors.append(boxAnchor)
    }

}

Alternatively if you wanted to do some heavy lifting (who doesn't!) then you could do something like this by creating a global variable which will reference the Entity that you have selected (which in this case is called Steel Box):

//Variable To Store Our Currently Selected Entity
var currentEntity: Entity?

Then using touchesBegan and touchesMoved you can something like this:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

  /* Get The Current Touch Location In The ARView
   Perform A HitTest For The Nearest Entity
   Checks That The Tapped Entity Is The Steel Box
   Set It As The Current Entity
   */
  guard let touchLocation = touches.first?.location(in: arView),
    let tappedEntity = arView.hitTest(touchLocation, query: .nearest, mask: .default).first?.entity,
    tappedEntity.name == "Steel Box" else {
      return
  }

  currentEntity = tappedEntity

}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

  /* Get The Current Touch Location In The ARView
   Perform A HitTest For An Existing Plane
   Move The Current Entity To The New Transfortm
   Set It As The Current Entity
   */
  guard let touchLocation = touches.first?.location(in: arView),
    let currentEntity = currentEntity else {
      return
  }

  if let transform = arView.hitTest(touchLocation, types: .existingPlaneUsingExtent).first?.worldTransform {
    currentEntity.move(to: transform, relativeTo: nil, duration: 0.1)

  }
}

Hope it helps point the in the right direction.

Upvotes: 5

Related Questions