Notice when node position enters certain bounding box in scene kit? - ios

is there a way to trigger a function, when a node (e.g. a tossed ball) enters a certain bounding box (e.g. a basket) in SceneKit / ARKit? If not, how would you implement this problem?

The way I would probably approach this. After obtaining a basketball hoop 3D dae model.
https://3dwarehouse.sketchup.com/model/fa42320b3def2c6b4741187cebbc52b3/Basketball-Hoop
I would first setup up the physicsShapes for the different elements.... backboard, pole & ring/hoop, floor & ball. I would then work out the cylinder size to fit inside the ring.
Using:
let cylinder = SCNCylinder(radius: 1.0, height: 0.1)
This becomes the physicBody for the hoop.
hoopNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry:cylinder))
I would then workout the collisions the ball has with each different basketball element/node.
Don’t forget to use the debug feature
SceneView.debugOptions = .showPhysicsShapes
This will help to visually debug the collisions with the physics shapes & make sure the physic shapes are accurately scaled and in the correct position.
How to do setup collisions has already been answered in a previous post here...
how to setup SceneKit collision detection
You probably want the ball to bounce off the backboard, pole and ring, but if it hits the hoop physic shape... you might want to make the ball disappear and create a nice spark/flame animation or something. You could then have a score that registers as well.
This Arkit game on github will give you a nice template for handling all those things, collisions, animations & scoring
https://github.com/farice/ARShooter

You have to implement it using the ball's world position and the basket world position + bounding box.
Calculate the size of the basket using it's bounding box:
var (basketBoundingBoxMin, basketBoundingBoxMax) = (basket?.presentation.boundingBox)!
let size = basketBoundingBoxMax - basketBoundingBoxMin
Use this size and the basket's world position to calculate:
Basket's minimum x,y,z
Basket's maximum x,y,z
Then check if ball's world position is inside the basket's minimum and maximum position.

Related

ARKit - positioning a node in the world relative to world origin

At the moment, my code loads in a 3D model, creates a node using that model, and displays the node in the scene. Setting the scale/rotation (euler angles) of the node works fine. However, I'm trying to set the position of the node relative to the world origin, and I don't want the node to be attached to a plane.
I've tried setting node.position and node.worldPosition to no avail; although the position of the node changes, when the camera moves, the node doesn't stay static, but moves about with the camera. I'm new to using ARKit, so I'm probably doing something stupid, but I can't figure out what it is that I need to do, so any help would be much appreciated.
Edit:
The weird thing is that if I set the coordinates to say SCNVector3(0, 3, 0) it's fine, but if I go over a certain number of meters away it seems to fail. Is this expected
Firstly, poor world tracking can be caused by these common issues.
Poor ligthing. Causes low number of feature points available for tracking
Lack of texture Causes low number of feature points available for tracking
Fast Movement Causes blurry images which causes tracking to fail
However, what I believe is happening in your case (which is a little more tricky to debug)... is you are most likely placing the loaded model underneath the detected horizontal plane in the scene.
In other words, you may have positioned the SCNNode using a negative Y coordinate which places the node below the horizontal detected plane and causes the model to drift around as you change the cameraView
try setting the Y position of the node to either 0 or a small positive value like 0.1 metres
node.position = SCNVector3(0, 0, -1) // SceneKit/AR coordinates are in meters
sceneView.scene.rootNode.addChildNode(node)
z = -1 places the SCNNode 1 metre in front of your cameraView.
Note: I verified this issue myself using a playground I use for testing purposes.

finding the depth in arkit with SCNVector3Make

the goal of the project is to create a drawing app. i want it so that when i touch the screen and move my finger it will follow the finger and leave a cyan color paint. i did created it BUT there is one problem. the paint DEPTH is always randomly placed.
here is the code, just need to connect the sceneView with the storyboard.
https://github.com/javaplanet17/test/blob/master/drawingar
my question is how do i make the program so that the depth will always be consistent, by consistent i mean there is always distance between the paint and the camera.
if you run the code above you will see that i have printed out all the SCNMatrix4, but i none of them is the DEPTH.
i have tried to change hitTransform.m43 but it only messes up the x and y.
If you want to get a point some consistent distance in front of the camera, you don’t want a hit test. A hit test finds the real world surface in front of the camera — unless your camera is pointed at a wall that’s perfectly parallel to the device screen, you’re always going to get a range of different distances.
If you want a point some distance in front of the camera, you need to get the camera’s position/orientation and apply a translation (your preferred distance) to that. Then to place SceneKit content there, use the resulting matrix to set the transform of a SceneKit node.
The easiest way to do this is to stick to SIMD vector/matrix types throughout rather than converting between those and SCN types. SceneKit adds a bunch of new accessors in iOS 11 so you can use SIMD types directly.
There’s at least a couple of ways to go about this, depending on what result you want.
Option 1
// set up z translation for 20 cm in front of whatever
// last column of a 4x4 transform matrix is translation vector
var translation = matrix_identity_float4x4
translation.columns.3.z = -0.2
// get camera transform the ARKit way
let cameraTransform = view.session.currentFrame.camera.transform
// if we wanted, we could go the SceneKit way instead; result is the same
// let cameraTransform = view.pointOfView.simdTransform
// set node transform by multiplying matrices
node.simdTransform = cameraTransform * translation
This option, using a whole transform matrix, not only puts the node a consistent distance in front of your camera, it also orients it to point the same direction as your camera.
Option 2
// distance vector for 20 cm in front of whatever
let translation = float3(x: 0, y: 0, z: -0.2)
// treat distance vector as in camera space, convert to world space
let worldTranslation = view.pointOfView.simdConvertPosition(translation, to: nil)
// set node position (not whole transform)
node.simdPosition = worldTranslation
This option sets only the position of the node, leaving its orientation unchanged. For example, if you place a bunch of cubes this way while moving the camera, they’ll all be lined up facing the same direction, whereas with option 1 they’d all be in different directions.
Going beyond
Both of the options above are based only on the 3D transform of the camera — they don’t take the position of a 2D touch on the screen into account.
If you want to do that, too, you’ve got more work cut out for you — essentially what you’re doing is hit testing touches not against the world, but against a virtual plane that’s always parallel to the camera and a certain distance away. That plane is a cross section of the camera projection frustum, so its size depends on what fixed distance from the camera you place it at. A point on the screen projects to a point on that virtual plane, with its position on the plane scaling proportional to the distance from the camera (like in the below sketch):
So, to map touches onto that virtual plane, there are a couple of approaches to consider. (Not giving code for these because it’s not code I can write without testing, and I’m in an Xcode-free environment right now.)
Make an invisible SCNPlane that’s a child of the view’s pointOfView node, parallel to the local xy-plane and some fixed z distance in front. Use SceneKit hitTest (not ARKit hit test!) to map touches to that plane, and use the worldCoordinates of the hit test result to position the SceneKit nodes you drop into your scene.
Use Option 1 or Option 2 above to find a point some fixed distance in front of the camera (or a whole translation matrix oriented to match the camera, translated some distance in front). Use SceneKit’s projectPoint method to find the normalized depth value Z for that point, then call unprojectPoint with your 2D touch location and that same Z value to get the 3D position of the touch location with your camera distance. (For extra code/pointers, see my similar technique in this answer.)

sceneKit dynamic physic body fall through floor

I have a cube with dynamic physical body and a plane with kinematic physic body.When I place a cube above plane, it will fall onto plane and a bounce is expected.
The PROBLEM is: when the cube is small or light, it just go through plane.For instance, cube has 0.1*0.1*0.1 work fine but 0.05*0.05*0.05 sucks.In this case I still get physical body contacting notification.
this is my code for creating geometry:
//cube
//when dimension is 0.1 everything is fine
float dimension = 0.05;
SCNBox *cube = [SCNBox boxWithWidth:dimension height:dimension length:dimension chamferRadius:0];
cube.materials = #[material];
SCNNode *node = [SCNNode nodeWithGeometry:cube];
node.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeDynamic shape:nil];
node.physicsBody.mass = 1;
node.physicsBody.categoryBitMask = phsicBodyCategoryCube;
node.physicsBody.collisionBitMask = phsicBodyCategoryPlane;
node.physicsBody.contactTestBitMask = phsicBodyCategoryPlane;
//plane
self.planeGeometry = [SCNBox boxWithWidth:100 height:0.01 length:100 chamferRadius:0
plane.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeKinematic
shape: [SCNPhysicsShape shapeWithGeometry:self.planeGeometry options:nil]];
plane.physicsBody.categoryBitMask = phsicBodyCategoryPlane;
plane.physicsBody.collisionBitMask = phsicBodyCategoryCube;
plane.physicsBody.contactTestBitMask = phsicBodyCategoryCube;
I’m surprised you’re getting collisions at all. See the SCNPhysicsBodyType docs – the kinematic type is for bodies that you move (by setting their position, etc) to cause collisions, but that aren’t themselves affected by other bodies colliding with them.
If you set the floor’s type to static, you’ll probably see better results. A static body doesn’t move (either as a result of physics interactions, or by you setting its position), so the physics engine can more accurately check for other bodies colliding with it.
edit: Other things to look into:
Use a SCNPlane rather than a SCNBox for your floor. (Note that you can create physics bodies with shapes different from their visible geometry, if that helps.) I'm not sure about SceneKit's, but it's easier for a lot of physics engines to check "has some object passed this infinite boundary since the last frame?" than "is some object inside this finite region on this frame?"
Enable precise collision detection for your moving objects.
Finally I found this is all about SceneKit's physical simulation. If the cube falls too fast, It will make a large penetration with plane In physical simulation steps. In this case sceneKit just let it fall through plane instead of collision. When I change cube's velocity to a small value It collided with plane.
My solution is to give cube a upward velocity when large over-penetration happens, which is the 30% of fall velocity. The trick is adjusting cube's velocity until a collision happens.
Works only for spherical physics shapes
For this issue SCNPhysicsBody has special attribute: continuousCollisionDetectionThreshold
For example, in a game involving projectiles and targets, a small
projectile may pass through a target if it moves farther than the
target's thickness within one time step. By setting the projectile's
continuousCollisionDetectionThreshold to match its diameter, you
ensure that SceneKit always detects collisions between the projectile
and other objects.

Detecting when a circle really intersects a node

I use ball.frame.intersects(bar.frame), where ball = SKShapeNode(circleOfRadius: BALL_RADIUS) and bar = SKShapeNode(rectOfSize: <Some CGSize object>)
While this works, there is an invisible square in which the circle is inscribed. The diameter of the circle is the same as the side of the square. This square is what ball.frame is, so sometimes, the ball will act like it's intersecting the bar, but visually it's not.
In this screenshot you can see that the ball is stopped, and therefore the game is over because ball.frame.intersects(bar.frame) returned true, even though visually the ball isn't even touching the bar.
So how do I check if the ball is really intersecting the bar, and not if the ball's frame is intersecting?
SpriteKit has decently powerful collision detection in its physics subsystem — you don't have to roll your own.
Create SKPhysicsBody objects for the ball and the bars, using init(circleOfRadius:) for the former and init(rectangleOfSize: for the latter.
Then, you can either:
Drive all the movement yourself, and use SKPhysics only to test for collisions. In this case you need to set your scene's physicsWorld.gravity to the zero vector, and call allContactedBodies() on the ball when you want to see if it's contacting a bar.
Let SKPhysics drive the movement, by setting velocities or applying impulses. In this case you'll need to set a contact delegate for your physics world, and then you'll get a callback whenever a collision occurs.
For more details on either approach, see the docs.
After detecting that ball frame intersects box frame:
Determine the radius of the ball
Determine the point on the line that is the edge of the ball from the center of the ball to the edge of the box. (Hint: pythagorean theorem - hypotenuse = radius)
Is this point inside the box's rect?
Repeat for each point in the box.

Setting Spritekit gravity source

Is there any way to set an x,y coordinate that gravity pulls towards?
We want to suck a number of objects with physicsbodies towards a point as if it was a black hole.
Not for the regular world gravity.
To achieve this effect, you have to apply to each body an impulse every frame. The strength and direction of the impulse depends on the distance of the body (node) to the gravity source's position.

Resources