How to connect two SCNSpheres in 3D space using Bezier path in Swift? - 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.

Related

SceneKit SCNPhysicsBody performance

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?

Show part of a circle

i want to display an arbitray part of a circle.
I know how to get a round View using layer.cornerRadius now i want to see only a part of that circle(for any given radiant value). It would be ok, if the rest of it would be simply hidden beneath something white.
Any ideas, how to achieve that?
edit:
i have written a class for my View:
class Circle:UIView {
var rad = 0
let t = CGFloat(3.0)
override func draw(_ rect: CGRect) {
super.draw(rect)
let r = self.frame.width / CGFloat(2)
let center = CGPoint(x: r, y: r)
let path = UIBezierPath()
path.move(to: CGPoint(x: t, y: r))
path.addLine(to: CGPoint(x: 0.0, y: r))
path.addArc(withCenter: center, radius: CGFloat(r), startAngle: CGFloat(Double.pi), endAngle: CGFloat(Double.pi+rad), clockwise: true)
let pos = path.currentPoint
let dx = r - pos.x
let dy = r - pos.y
let d = sqrt(dx*dx+dy*dy)
let p = t / d
path.addLine(to: CGPoint(x: pos.x + p * dx, y: pos.y + p * dy))
path.addArc(withCenter: center, radius: r-t, startAngle: CGFloat(Double.pi+rad), endAngle: CGFloat(Double.pi), clockwise: false)
UIColor(named: "red")?.setFill()
path.fill()
}
}
public func setRad(perc:Double) {
rad = Double.pi * 2 * perc
}
in my view controller i call
circleView.layer.cornerRadius = circleView.frame.size.width / 2
circleView.clipsToBounds = true
circleView.layer.borderWidth = 1
circleView.layer.borderColor = UIColor.darkGray.cgColor
circleView.layer.shadowColor = UIColor.black.cgColor
circleView.layer.shadowOpacity = 1
circleView.layer.shadowOffset = CGSize.zero
circleView.layer.shadowRadius = 3
circleView.layer.masksToBounds = false
circleView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTap)))
now i get the full square View with a black circle from the corner and the red arc that i draw. If necessary i will post a picture tomorrow
One way to do it is to draw a UIBezierPath.
If you just want an arc, you can call the init(arcCenter:radius:startAngle:endAngle:clockwise:) initializer. Remember to specify the start and end angles in radians!
After that, you can set the stroke color and call stroke
If you want a sector of a circle, you can create a UIBezierPath with the parameterless initializer, then move(to:) the center of the circle, and then addLine(to:) the start of the arc. You can probably calculate where this point is with a bit of trigonometry. After that, call addArc like I described above, then addLine(to:) the point where you started. After that, you can fill the path.

Move nodes by x and by y at the same time

I want to create a game with rectangles which move by x and by y.
My question is: Have I move the view (viewer of the player) or have I move the rectangles by x and by y at the same time?
I tried to move rectangles by x and by y at the same time, with the following code, but the rectangles move only by y.
#objc func addRects() {
rects = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: rects) as! [String]
rect = SKSpriteNode(imageNamed: rects[0])
rect.position = CGPoint(x: self.frame.width / 256 + 320, y: self.frame.height / 256)
rect.size = CGSize(width: 180, height: 120)
rect.zPosition = 1
self.addChild(rect)
moveByX = SKAction.moveTo(x: -500, duration: 2.0)
moveByY = SKAction.moveTo(y: -800, duration: 2.0)
removeRects = SKAction.removeFromParent()
wait = SKAction.wait(forDuration: 2.0)
rect.run(SKAction.sequence([moveByY,moveByY]))
rect.run(SKAction.sequence([wait,removeRects]))
}
How can I resolve this problem ?
Thank you
Don't use a sequence:
rect.run(moveByY)
rect.run(moveByX)
then it should move at the same time "moveByY" and "moveByX"
The other problem is, that you used:
rect.run(SKAction.sequence([moveByY,moveByY]))
I Hope my answer helps!

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

What unit does Swift use for angles?

Am am writing some code to draw circular charts, and while reading some example code, became confused about how angles are measured in Swift.
In the first post I read, Swift seems to use radians:
CGContextAddArc(context, origo.x, origo.y, radius, floatPi * 3 / 2, floatPi * 3 / 2 + floatPi * 2 * percent, 0)
In the second post, Swift seems to define angles as a range from 0 to 1:
let circlePath = UIBezierPath(ovalInRect: CGRect(x: 200, y: 200, width: 150, height: 150))
var segments: [CAShapeLayer] = []
let segmentAngle: CGFloat = (360 * 0.125) / 360
for var i = 0; i < 8; i++ {
let circleLayer = CAShapeLayer()
circleLayer.path = circlePath.CGPath
// start angle is number of segments * the segment angle
circleLayer.strokeStart = segmentAngle * CGFloat(i)
// end angle is the start plus one segment, minus a little to make a gap
// you'll have to play with this value to get it to look right at the size you need
let gapSize: CGFloat = 0.008
circleLayer.strokeEnd = circleLayer.strokeStart + segmentAngle - gapSize
circleLayer.lineWidth = 10
circleLayer.strokeColor = UIColor(red:0, green:0.004, blue:0.549, alpha:1).CGColor
circleLayer.fillColor = UIColor.clearColor().CGColor
// add the segment to the segments array and to the view
segments.insert(circleLayer, atIndex: i)
view.layer.addSublayer(segments[i])
}
So, how does Swift measure angles?
Thanks
Swift has absolutely nothing to do with the angle unit, it's just a Float/Double to it. It's the functions of the Core Graphics framework that specify what angle unit they're expecting. Just read the docs of the functions in question.
From the CGContextAddArc docs:
startAngle: The angle to the starting point of the arc, measured in radians from the positive x-axis.
endAngle: The angle to the end point of the arc, measured in radians from the positive x-axis.
Also the strokeStart and strokeEnd properties of the CAShapeLayer say what part of the given path should be drawn, it's not an angle. Just read the docs next time.