如何使用Swift Scenekit Contact Test

huangapple go评论71阅读模式
英文:

How to use Swift Scenekit Contact Test

问题

我正在开发一个游戏,用户可以使用手指移动物体,当用户释放手指时,游戏会检查物体之间是否重叠,如果有重叠,则将移动的物体移回到其原始位置。我正在使用SCNScene.PhysicsWorld.contactTest(with: )来检查节点之间的重叠。然而,当我将物体的物理体形状从.convexHull更改为.concavePolyHedron时,一切都停止工作,不会报告任何接触。我已将物理体设置为静态,所以我不知道该怎么办。

这是我为每个节点配置物理体的代码:

parentNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(node: parentNode, options: [.type: SCNPhysicsShape.ShapeType.concavePolyhedron, .collisionMargin: 0.0, .scale: scaleVector]))

这是我调用接触测试的代码:

if let test = currentNode?.physicsBody {
     let list = view.scene!.physicsWorld.contactTest(with: test) {
          ...
     }
}

编辑:这是一个演示我的问题的基本游戏的GameViewController。当使用concavePolyHedron时,碰撞检测不起作用,但将选项更改为convexHull时,代码可以正常工作。

英文:

I am developing a game where users can use their finger to move objects, and when the user releases their finger the game checks for overlap between objects and move the moved object back to it's original position if there is overlap. I am using SCNScene.PhysicsWorld.contactTest(with: ) to check for overlap between my nodes. However, the method only works correctly when nodes have physicsbodys using .convexHull, when I change it to .concavePolyHedron everything stops working and no contact is reported. I have set the physicbodys to be static so I am at a loss of what to do.

Here is my code configuring the physicsbody for each node

parentNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(node: parentNode, options: [.type: SCNPhysicsShape.ShapeType.concavePolyhedron, .collisionMargin: 0.0, .scale: scaleVector]))

Here is my code calling contact test :

if let test = currentNode?.physicsBody {
     let list = view.scene!.physicsWorld.contactTest(with: test) {
          ...
     }
}

Edit: Here is a GameViewController of a basic game demonstrating my problem. The collision detection does not work with concavePolyHedron but changing the option to convexHull the code works correctly.

import UIKit
import QuartzCore
import SceneKit

class GameViewController: UIViewController {
  
  var dragGesture: UIPanGestureRecognizer?
  var currentNode: SCNNode?
  var beginningPos: SCNVector3?
  var previousLoc: CGPoint?
  var scnView: SCNView?
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    // create a new scene
    let scene = SCNScene()
    
    // create and add a camera to the scene
    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    scene.rootNode.addChildNode(cameraNode)
    
    // place the camera
    cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
    
    
    // create and add a light to the scene
    let lightNode = SCNNode()
    lightNode.light = SCNLight()
    lightNode.light!.type = .omni
    lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
    scene.rootNode.addChildNode(lightNode)
    
    // create and add an ambient light to the scene
    let ambientLightNode = SCNNode()
    ambientLightNode.light = SCNLight()
    ambientLightNode.light!.type = .ambient
    ambientLightNode.light!.color = UIColor.darkGray
    scene.rootNode.addChildNode(ambientLightNode)
    
    
    // retrieve the SCNView
    scnView = self.view as! SCNView
    
    // set the scene to the view
    scnView?.scene = scene
    
    // allows the user to manipulate the camera
    scnView?.allowsCameraControl = true
    
    // show statistics such as fps and timing information
    scnView?.showsStatistics = true
    
    // configure the view
    scnView?.backgroundColor = UIColor.black
    
    let node1 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
    node1.position = SCNVector3(x: 0, y: 0, z: 0)
    node1.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(node: node1, options:
                                                                              [.type: SCNPhysicsShape.ShapeType.concavePolyhedron, .collisionMargin: 0.0,]))
    scene.rootNode.addChildNode(node1)
    
    
    let node2 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
    node2.position = SCNVector3(x: 2, y: 2, z: 0)
    node2.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(node: node2, options:
                                                                              [.type: SCNPhysicsShape.ShapeType.concavePolyhedron, .collisionMargin: 0.0,]))
    scene.rootNode.addChildNode(node2)
    
    
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
    scnView?.addGestureRecognizer(tapGesture)
    
    
  }
  
  func setupGesture() {
    dragGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
    view.addGestureRecognizer(dragGesture!)
  }
  
  func removeGesture() {
    if let dragGesture = dragGesture {
      view.removeGestureRecognizer(dragGesture)
      self.dragGesture = nil // Set the dragGesture to nil after removing it
    }
  }
  
  @objc
  func handlePan(_ sender: UIPanGestureRecognizer) {
    var delta = sender.translation(in: self.view)
    let loc = sender.location(in: self.view)
    
    
    if sender.state == .began {
      if let location = currentNode?.position {
        beginningPos = location
        previousLoc = loc
      }
    }
    
    if sender.state == .changed {
      if let currentNode {
        delta = CGPoint.init(x: 2 * (loc.x - previousLoc!.x), y: 2 * (loc.y - previousLoc!.y))
        currentNode.position = SCNVector3.init(currentNode.position.x  + Float(delta.x * 0.0075), currentNode.position.y - Float(delta.y * (0.0075)), currentNode.position.z)
      }
      previousLoc = loc
    }
    
    
    if sender.state == .ended {
      if let test = currentNode?.physicsBody {
        let list = scnView?.scene?.physicsWorld.contactTest(with: test)
        
        if list!.count > 0 {
          if let beginningPos {
            currentNode?.position = beginningPos
            
          }
        }
      }
    }
    
  }
  
  @objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    
    // check what nodes are tapped
    let p = gestureRecognize.location(in: scnView)
    let hitResults = scnView?.hitTest(p, options: [:])
    
    if let node = currentNode {
      
      removeGesture()
      scnView?.allowsCameraControl = true
      
      
      if let material = node.geometry?.firstMaterial {
        SCNTransaction.begin()
        SCNTransaction.animationDuration = 0.5
        
        material.multiply.contents = UIColor.white
        
        SCNTransaction.commit()
      }
      currentNode = nil
    }
    
    // check that we clicked on at least one object
    if hitResults!.count > 0 {
      // retrieved the first clicked object
      
      currentNode = hitResults!.first?.node
      
      
      
      scnView?.allowsCameraControl = false
      
      
      
      setupGesture()
      
      
      let pos = currentNode!.position
      
      
      if let material = currentNode?.geometry?.firstMaterial {
        
        
        // highlight it
        SCNTransaction.begin()
        SCNTransaction.animationDuration = 0.5
        
        material.multiply.contents = UIColor.red
        SCNTransaction.commit()
      }
    }
  }
  
  
  
  override var prefersStatusBarHidden: Bool {
    return true
  }
  
  override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    if UIDevice.current.userInterfaceIdiom == .phone {
      return .allButUpsideDown
    } else {
      return .all
    }
  }
  
}

答案1

得分: 0

的确,convexHullconcavePolyhedron 的行为在这里有点奇怪。

我猜想,你的目标是将一个立方体放在现有的立方体上方(或其他位置)。根据你的配置,表面的边缘超出了实际立方体大小的微小距离,因此即使没有触碰现有立方体,立方体也会翻转到其原始位置。我稍微尝试了一下,这是我取得的最佳结果:

let node1 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
node1.position = SCNVector3(x: 0, y: 0, z: 0)
node1.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: node1.geometry!))
scene.rootNode.addChildNode(node1)

let node2 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
node2.position = SCNVector3(x: 2, y: 2, z: 0)
node2.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: node2.geometry!))
scene.rootNode.addChildNode(node2)

正如你所看到的,我没有对 physicsBody 进行特殊的配置。现在我能够将这两个立方体尽可能地靠拢。

使用:SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: node2.geometry!))

希望这对你有所帮助。

英文:

Indeed, the behaviour of that convexHull and concavePolyhedron are a little bit strange here.

I suppose, your goal is to place one cube like on top of the existing one (or where ever.) With your config the margin at the surface overlapped the real cube size by a tiny distance, so the cube flips to its original position, even when it did not touch the existing cube. I played around a little bit and this is my best result, I achieved:

  let node1 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
  node1.position = SCNVector3(x: 0, y: 0, z: 0)
  node1.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: node1.geometry!))
  scene.rootNode.addChildNode(node1)
  
  
  let node2 = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
  node2.position = SCNVector3(x: 2, y: 2, z: 0)
  node2.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: node2.geometry!))
  scene.rootNode.addChildNode(node2)

As you can see, I do not force some special configuration to the physicsBody. And this is the most close, I can move the cubes together now.

Using: SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: node2.geometry!))

如何使用Swift Scenekit Contact Test

I hope it will help.

huangapple
  • 本文由 发表于 2023年7月11日 01:26:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76656013.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定