Reputation: 2691
I have several nodes in my scene which i can rotate and drag around. I've set up a physicsWorld delegate, added physicsBody with type kinematic to my nodes and check for physicsWorld didBeginContact - up to this point everything is ok, when i move the nodes , the contact begin/end fires.
The way i'm trying to handle this is to set a bool var that change to true once the contact start to prevent further movement, but i'm running into cases (especialy if i drag the node too fast) , that the node gets a little bit inside of the other object.
should i use different approach to this? I don't really want a collision, just the other node to act "solid" and not allow intersection even at higher speed contact.
EDIT:
Some sample images to further clarify the issue:
For simplicity i've added only 2 nodes to demonstrate the issue. First image is initial position, 2nd and 3rd (side view) are after a very fast pan to the right. The contact detection was fired only after nodes were already intersected.
One approach that I tried was grabbing the last position before the contact is triggered and reseting the node position after contact is detected, but the result is very choppy and unstable and for a split second you can sometimes see the objects intersecting anyway before jumping to the last "good" position. I feel there must be some easier way to achieve this, but after spending hours going through available resources i can't figure it out.
EDIT 2
further research pointed down the line of sweep testing, technicaly if i can detect possible collision before i move the node, i should be able to restrict the movement to stop before the intersetion occurs
UPDATE: another dead end, as Xcode points out
Error: convexSweep only works with convex shapes
Upvotes: 2
Views: 966
Reputation: 2691
After a lot of days of headbanging and almost giving up, i re-re-reread the physicsWorld documentation and finally found what i was overlooking all the time - the contactTest method that can be manualy triggered at any time, independent of render loop. I use it in the renderer(_:willRenderScene:atTime:) in order to "fix" the overlap before the scene gets rendered.
My scene is bit more complicated than the example, but with few extra tweeks i almost got it working now. I'm not sure if this is the proper solution and how costy it will become performance wise, but for now i will settle down with this so i can move on with the development.
The relevant code in case somebody runs in similar situation:
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
// make sure we have active node and pan direction
if(selectedBrickNode != nil && self.panDirection != nil){
// contactTest
let pw = scnScene.physicsWorld
let node = selectedBrickNode.node!
let contacts = pw.contactTest(with: node.physicsBody!, options: nil)
var axisVector:SCNVector3
// specify which axis we want to correct
switch self.panDirection!{
case "right","left": axisVector = SCNVector3Make(1,0,0)
default: axisVector = SCNVector3Make(0,1,0);
}
for contact in contacts {
// round contact normal to get a unit vector
let cn = SCNVector3( round(contact.contactNormal.x),
round(contact.contactNormal.y),
round(contact.contactNormal.z))
// fix only for pan direction axis
if abs(cn.x) == axisVector.x && abs(cn.y)==axisVector.y {
let normal = contact.contactNormal
let transform = SCNMatrix4MakeTranslation( round(normal.x) * -Float(contact.penetrationDistance),
round(normal.y) * -Float(contact.penetrationDistance),
round(normal.z) * -Float(contact.penetrationDistance))
node.transform = SCNMatrix4Mult(node.transform, transform)
// break to prevent repeated contacts
break;
}
}
}
}
Upvotes: 4