SceneKit SCNPhysicsBody performance - swift

SceneKit SCNPhysicsBody performance doesn't seem to take advantage of the multiple cores.
I created a quick bit of code that puts a bunch of small spheres on top of a cube where they drop under gravity. As the number of spheres increases things get very slow. I don't see a way of getting optimal multicore performance. Anyone have any ideas?
I did some quick tests with a stopwatch and observing the simulation to a specific point launching 1,5 and 8 instances of the app.
1 instance running : 15 seconds
2 instances running : 25 seconds
8 instances running : 35 seconds
It is clear that the multcores are not being used optimally for simulation with just one instance.
func createCubeWithSpheres(bottomLeft: SCNVector3, topRight: SCNVector3, radiusOfSpheres: Double) -> SCNNode {
// Create the box using the bottomLeft and topRight coordinates
let box = SCNBox(
width: topRight.x - bottomLeft.x,
height: topRight.y - bottomLeft.y,
length: topRight.z - bottomLeft.z,
chamferRadius: 0.0
)
let node = SCNNode(geometry: box)
// Add a static physics body to the box
node.physicsBody = SCNPhysicsBody(type: .static, shape: nil)
// Calculate the dimensions of the box
let width = box.width
let height = box.height
let depth = box.length
// Calculate the maximum number of spheres that can fit inside the box
let maxNumSpheresWidth = Int(width / (radiusOfSpheres * 2))
let maxNumSpheresHeight = Int(height / (radiusOfSpheres * 2))
let maxNumSpheresDepth = Int(depth / (radiusOfSpheres * 2))
let maxNumSpheres = maxNumSpheresWidth * maxNumSpheresHeight * maxNumSpheresDepth
// Create an array to hold the spheres
var spheres = [Sphere]()
// Create the spheres and add them to the array
for zIndex in 0..<maxNumSpheresDepth {
for yIndex in 0..<maxNumSpheresHeight {
for xIndex in 0..<maxNumSpheresWidth {
let xPos = bottomLeft.x + radiusOfSpheres + (radiusOfSpheres * 2) * CGFloat(xIndex)
let yPos = bottomLeft.y + radiusOfSpheres + (radiusOfSpheres * 2) * CGFloat(yIndex)
let zPos = bottomLeft.z + radiusOfSpheres + (radiusOfSpheres * 2) * CGFloat(zIndex)
let sphere = Sphere(radius: radiusOfSpheres, position: SCNVector3(xPos, yPos, zPos))
spheres.append(sphere)
}
}
}
// Add the spheres to the box's node
for sphere in spheres {
node.addChildNode(sphere.node)
}
return node
}
class Sphere {
let node = SCNNode()
// Initialize the sphere with a radius and position.
init(radius: CGFloat, position: SCNVector3? = nil) {
node.geometry = SCNSphere(radius: radius)
if let position = position {
node.position = position
}
// Add a physics body to the sphere so that it can interact with other objects.
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
// Add a "stickiness" to the sphere by applying a static electric field to it.
// let electricField = SCNPhysicsField.electric()
// electricField.strength = 10 // Adjust this value to increase or decrease the stickiness of the sphere.
// node.physicsField = electricField
}
}
let box = createCubeWithSpheres(bottomLeft: SCNVector3(x: 0.0, y: 0.0, z: 0.0), topRight: SCNVector3(x: 5.0, y: 5.0, z: 5.0), radiusOfSpheres: 0.1)
scene.rootNode.addChildNode(box)
Anyway to speed this up with one instance running without giving up collision accuracy?

Related

swift SceneKit calculate node angle

i trying to add a node (pin) to sphere node (when taping on sphere node), but can't correctly calculate an angle, can u help me pls ?
private func addPinToSphere(result: SCNHitTestResult) {
guard let scene = SCNScene(named: "art.scnassets/tree.scn") else { return }
guard let treeNode = scene.rootNode.childNode(withName: "Trunk", recursively: true) else { return }
let x = CGFloat(result.worldCoordinates.x)
let y = CGFloat(result.worldCoordinates.y)
treeNode.position = result.worldCoordinates
treeNode.eulerAngles = SCNVector3((x) * .pi / CGFloat(180), 0, (y - 90) * .pi / CGFloat(180)) // y axis is ignorable
result.node.addChildNode(treeNode)
}
when run code i have this
but want like this
You can do it similar to this code. This is a missile launcher, but it's basically the same thing. Your pin = my fireTube, so create a base node, then add a subnode with your materials to it and rotate it into place. You'll have to experiment with where to place the tube initially, but once you get it into the right place the lookat constraint will always point it in the right direction.
case .d1, .d2, .d3, .d4, .d5, .d6, .d7, .d8, .d9, .d10:
let BoxGeometry = SCNBox(width: 0.8, height: 0.8, length: 0.8, chamferRadius: 0.0)
let vNode = SCNNode(geometry: BoxGeometry)
BoxGeometry.materials = setDefenseTextures(vGameType: vGameType)
let tubeGeometry = SCNTube(innerRadius: 0.03, outerRadius: 0.05, height: 0.9)
let fireTube = SCNNode(geometry: tubeGeometry)
tubeGeometry.firstMaterial?.diffuse.contents = data.getTextureColor(vTheme: 0, vTextureType: .barrelColor)
fireTube.position = SCNVector3(0, 0.2, -0.3)
let vRotateX = SCNAction.rotateBy(x: CGFloat(Float(GLKMathDegreesToRadians(-90))), y: 0, z: 0, duration: 0)
fireTube.runAction(vRotateX)
vNode.addChildNode(fireTube)
return vNode
Then set target on your base node and your subnode will rotate with it:
func setTarget()
{
node.constraints = []
let vConstraint = SCNLookAtConstraint(target: targetNode)
vConstraint.isGimbalLockEnabled = true
node.constraints = [vConstraint]
}
In your case, target equals center mass of your sphere and the pin will always point to it, provided you built and aligned your box and pin correctly.

Shooting balls from a rotating cannon

I'm making a SpriteKit game. I have six cannons that I've made rotate back and fourth. I want to shoot cannonballs from each cannon that sync with the rotation of each cannon. I want a duration of one second between each cannonball.
So basically: A cannon that is in constant rotation is shooting cannonballs in the same direction as the rotation.
For the cannons i'm using an extension:
extension CGFloat {
func degreesToRadians() -> CGFloat {
return self * CGFloat.pi / 180
}
}
I'm gonna put the code for just one cannon, since I should be able to figure out how to adjust one of the cannonball movements to the others. Here is one:
func createCannons() {
let cannonLeftBottom = SKSpriteNode(imageNamed: "Cannon")
cannonLeftBottom.anchorPoint = CGPoint(x: 0.5, y: 0.5)
cannonLeftBottom.position = CGPoint(x: -325, y: -420)
cannonLeftBottom.zPosition = 4
cannonLeftBottom.setScale(0.4)
cannonLeftBottom.zRotation = CGFloat(65).degreesToRadians()
let rotateLB = SKAction.rotate(byAngle:
CGFloat(-65).degreesToRadians(), duration: 2)
let rotateBackLB = SKAction.rotate(byAngle:
CGFloat(65).degreesToRadians(), duration: 2)
let repeatRotationLB =
SKAction.repeatForever(SKAction.sequence([rotateLB,rotateBackLB]))
cannonLeftBottom.run(repeatRotationLB)
self.addChild(cannonLeftBottom)
}
Here is my function for the cannonball:
func createBalls() {
let cannonBallLB = SKSpriteNode(imageNamed: "Ball")
cannonBallLB.name = "CannonBall"
cannonBallLB.position = CGPoint(x: -325, y: -420)
cannonBallLB.physicsBody = SKPhysicsBody(circleOfRadius:
cannonBallLB.size.height / 2)
cannonBallLB.physicsBody?.affectedByGravity = false
cannonBallLB.zPosition = 3
cannonBallLB.setScale(0.1)
self.addChild(cannonBallLB)
}
THX!
You need to convert from Polar Coordinates to Rectangular Coordinates.
You do this by using sin and cos
E.G.
let speed = 100 //This would mean move 100 points per second
let force = CGVector(dx:cos(cannon.zRotation) * speed,dy:sin(cannon.zRotation) * speed)
cannonBall.applyForce(force)
Note: Now unless they changed this, force used to be in units of points, if they fixed it to units of meters, then you need to divide your speed by 150, since 150 points = 1 meter in Spritekit

How to connect two SCNSpheres in 3D space using Bezier path in Swift?

I have the following code:
func createScene(){
count += 1
let sphereGeom = SCNSphere(radius: 1.5)
sphereGeom.firstMaterial?.diffuse.contents = UIColor.redColor()
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0, y: 0))
let radius = 3.0
var radians = Double(0)
var yPosition = Float(5.4)
while count <= 20 {
if radians >= 2{
radians -= 2
}
let sphereNode = SCNNode(geometry: sphereGeom)
let angle = Double(radians * M_PI)
let xPosition = Float(radius * cos(angle))
let zPosition = Float(radius * sin(angle))
sphereNode.position = SCNVector3(xPosition, yPosition, zPosition)
let cgX = CGFloat(xPosition)
let cgY = CGFloat(yPosition)
path.addQuadCurveToPoint(CGPoint(x: cgX, y: cgY), controlPoint: CGPoint(x: (cgX / 2), y: (cgY / 2)))
path.addLineToPoint(CGPoint(x: (cgX - (cgX * 0.01)), y: cgY))
path.addQuadCurveToPoint(CGPoint(x: 1, y: 0), controlPoint: CGPoint(x: (cgX / 2), y: ((cgY / 2) - (cgY * 0.01))))
let shape = SCNShape(path: path, extrusionDepth: 3.0)
shape.firstMaterial?.diffuse.contents = UIColor.blueColor()
let shapeNode = SCNNode(geometry: shape)
shapeNode.eulerAngles.y = Float(-M_PI_4)
self.rootNode.addChildNode(shapeNode)
count += 1
radians += 0.5556
yPosition -= 1.35
self.rootNode.addChildNode(sphereNode)
}
I want to add a Bezier path connecting each sphere to the next one, creating a spiral going down the helix. For some reason, when I add this code, the shape doesn't even appear. But when I use larger x and y values, I see the path fine, but it is no way oriented to the size of the spheres. I don't understand why it disappears when I try to make it smaller.
Your SCNShape doesn't ever get extruded. Per Apple doc,
An extrusion depth of zero creates a flat, one-sided shape.
With larger X/Y values your flat shape happens to become visible. You can't build a 3D helix with SCNShape, though: the start and end planes of the extrusion are parallel.
You'll have to use custom geometry, or approximate your helix with a series of elongated SCNBox nodes. And I bet someone out there knows how to do this with a shader.

SKPhysicsJointFixed in SpriteKit and Swift

I'm making a game in Sprite Kit and Swift and I have a Sprite at the bottom of the screen and falling Sprites from the top which I want to catch and stick to the Sprite at the bottom, so I'm trying to use SKPhysicsJointFixed but when the objects collide instead of the falling object sticking to the one at the bottom which is supposed to catch and have it attached, it seems as if the bottom Sprite adapts the physics of the falling sprite and then falls off the screen with it. Here's the code I have in my didBeginContact method. and skewer is the name of the Sprite at the bottom which should always be at the bottom and not disappear.
if contact.bodyA.node!.name == "Skewer"
{
let boundX = skewer.physicsBody?.node?.position.x
let fixedJoint = SKPhysicsJointFixed.jointWithBodyA(contact.bodyA.node!.physicsBody, bodyB: contact.bodyB.node!.physicsBody, anchor: CGPoint(x: boundX!, y: boundY))
physicsWorld.addJoint(fixedJoint)
// contact.bodyB.node!.removeFromParent()
}
else
{
contact.bodyA!.node!.removeFromParent()
}
and the physics for the bottom screen Sprite are here
func makeSkewer()
{
skewer.name = "Skewer"
skewer.position = CGPoint(x: size.width * 0.5, y: size.height * 0.244)
skewer.physicsBody = SKPhysicsBody(rectangleOfSize: skewer.size)
skewer.physicsBody?.affectedByGravity = false
skewer.physicsBody?.categoryBitMask = kSkewerCategory
skewer.physicsBody?.contactTestBitMask = kFoodCategory
skewer.physicsBody?.collisionBitMask = kSceneEdgeCategory
addChild(skewer)
}
and physics for the falling Sprites are here
func random() ->CGFloat
{
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat,max: CGFloat) -> CGFloat
{
return random() * (max - min) + min
}
func addFood()
{
let randomCatchIndex = Int(arc4random_uniform(UInt32(foods.count)))
let food = SKSpriteNode(imageNamed: foods[randomCatchIndex])
let actualX = random(min: food.size.width/2, max: size.width - food.size.width/2)
let actualDuration = random(min: CGFloat(1.5), max: CGFloat(8.0))
let actionMove = SKAction.moveTo(CGPoint(x: actualX, y: -food.size.height/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
food.physicsBody = SKPhysicsBody(rectangleOfSize: food.size)
food.position = CGPoint(x: actualX, y: size.height + food.size.height/2)
food.physicsBody?.categoryBitMask = kFoodCategory
food.physicsBody?.contactTestBitMask = kSkewerCategory
food.physicsBody?.collisionBitMask = 0x0
food.physicsBody?.dynamic = true
food.runAction(SKAction.sequence([actionMove, actionMoveDone]))
addChild(food)
}
Set the skewer to not have dynamic physics. What you have currently is it not being affected by gravity, and as soon as it locks onto the food (which is traveling down and has momentum), the skewer moves with it.
In the creation of the skewer, run the following line:
skewer.physicsBody?.dynamic = false
You can also now ignore the affectedByGravity as that is something that only affects dynamic objects.

Give an SKNode its own physics

Is there a way to give an SKNode its own physics? I have an SKShapeNode call "backGround" which I use for the parent node of most of my other nodes. I am constantly moving "background" to the left, to give the illusion that the player is moving forward. However, one of the objects that has "backGround" as a parent node is a pin with a rope hanging from it. When background accelerates to the left, is there a way to make it so the rope doesn't swing back and forth, as ropes tend to do when accelerating or decelerating?
EDIT: Here is my code:
func createRopeNode(pos: CGPoint) -> SKSpriteNode{
let ropeNode = SKSpriteNode(imageNamed: "Ball")
ropeNode.size = CGSize(width: 5, height: 5)
ropeNode.physicsBody = SKPhysicsBody(rectangleOfSize: ropeNode.size)
ropeNode.physicsBody?.affectedByGravity = true
ropeNode.physicsBody?.collisionBitMask = 0
ropeNode.alpha = 1
ropeNode.position = CGPoint(x: pos.x + 0, y: pos.y)
ropeNode.name = "RopePiece"
let text = SKSpriteNode(imageNamed: "RopeTexture")
ropeNode.zPosition = -5
text.runAction(SKAction.rotateByAngle(atan2(-dx!, dy!), duration: 0))
ropeNode.addChild(text)
return ropeNode
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
if (!playerIsConnected){
playerIsConnected = true
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
dx = pin.position.x - playerPoint!.x
dy = pin.position.y - playerPoint!.y
let length = sqrt(pow(dx!, 2) + pow(dy!, 2))
let distanceBetweenRopeNodes = 5
let numberOfPieces = Int(length)/distanceBetweenRopeNodes
var ropeNodes = [SKSpriteNode]()
//adds the pieces to the array at respective locations
for var index = 0; index < numberOfPieces; ++index{
let point = CGPoint(x: pin.position.x + CGFloat((index) * distanceBetweenRopeNodes) * sin(atan2(dy!, -dx!) + 1.5707), y: pin.position.y + CGFloat((index) * distanceBetweenRopeNodes) * cos(atan2(dy!, -dx!) + 1.5707))
let piece = createRopeNode(point)
ropeNodes.append(piece)
world.addChild(ropeNodes[index])
}
let firstJoint = SKPhysicsJointPin.jointWithBodyA(ropeNodes[0].physicsBody, bodyB: pin.physicsBody, anchor:
CGPoint(x: (ropeNodes[0].position.x + pin.position.x)/2, y: (ropeNodes[0].position.y + pin.position.y)/2))
firstJoint.frictionTorque = 1
self.physicsWorld.addJoint(firstJoint)
for var i = 1; i < ropeNodes.count; ++i{
let nodeA = ropeNodes[i - 1]
let nodeB = ropeNodes[i]
let middlePoint = CGPoint(x: (nodeA.position.x + nodeB.position.x)/2, y: (nodeA.position.y + nodeB.position.y)/2)
let joint = SKPhysicsJointPin.jointWithBodyA(nodeA.physicsBody, bodyB: nodeB.physicsBody, anchor: middlePoint)
joint.frictionTorque = 0.1
self.physicsWorld.addJoint(joint)
}
finalJoint?.frictionTorque = 1
finalJoint = SKPhysicsJointPin.jointWithBodyA(ropeNodes[ropeNodes.count - 1].physicsBody, bodyB: player.physicsBody, anchor:
CGPoint(x: (ropeNodes[ropeNodes.count - 1].position.x + playerPoint!.x)/2, y: (ropeNodes[ropeNodes.count - 1].position.y + playerPoint!.y)/2))
self.physicsWorld.addJoint(finalJoint!)
}
}
else{
physicsWorld.removeJoint(finalJoint!)
playerIsConnected = false
}
}
Anchor points are what you are looking for. Move the anchor point of the scene to only move the "camera" of the scene (what is displayed onscreen). This will not jostle the pin and rope. Keep in mind that the anchor point is on a slightly different scale from the scene.
Where the width of the scene could be 1024, the "width" of the of the anchor point for one scene length is 1 (basically counting as one width of the node). Same for the height, where it could be 768, the "height" would still be 1 in the anchor point coordinate space. So to move half a screen width, move the anchor point 0.5
The anchor point is a CGPoint, so you can go vertically as well. Here's a quick example:
var xValue : Float = 0.75
var yValue : Float = 0.0
self.scene?.anchorPoint = CGPointMake(xValue, yValue);
And for further reading, here's a link to the documentation on anchor points for sprites.