Tuesday, March 24, 2015

Code Jam : Swift Physics, Joints, FieldNodes.

This week, I created something similar to pendulum, with Physics bodies, physics joints, and Field Nodes.

I say something similar, because it did not reach the exact pendulum swing that I wanted. But it is closer. If the values are tweaked a bit, it would reach the optimal pendulum swing motion. Before that I thought I would publish this post.

First create a simple empty game in Xcode for swift language.  In GameScene.swift, inside class GameScene, in didMoveToView(), add the following nodes.

/* Setup your scene here */
        let myLabel = SKLabelNode(fontNamed:"Chalkduster")
        myLabel.text = "Pendulum";
        myLabel.fontSize = 15;
        myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
        self.addChild(myLabel)

        
These lines create label node, set its font size, and mention the idea of the game.  Though the empty project is a game. This is no where closer to being a game. This is purely for experimentation and blogging purpose. 

Place the label at the center of the screen. CGRectGetMidX() is a CGGeometry function which returns center x position of the rectangle passed as the parameter. Similarly CGRectGetMidY. Csdfs


I created three main nodes. One is a rectangle which will act as the rod. A circle which will act as the bob. And an invisible circle which will act as the anchor point for the rod. It will be on the other side of the rod, on opposite end to the bob circle. 

        let rectNode = SKShapeNode(rect: CGRectMake(500,300, 5, 250))
        rectNode.strokeColor = SKColor.greenColor()
        rectNode.fillColor = SKColor.yellowColor();
        rectNode.lineWidth = 1
        let rectBody = SKPhysicsBody(rectangleOfSize: rectNode.frame.size)
        rectBody.affectedByGravity = false
        rectBody.mass  = 5
        rectBody.dynamic = true // should be dynamic
        rectNode.physicsBody = rectBody;
        self.addChild(rectNode)

This snippet creates a rectangle node positioned at 500,300. Stroke color is green, fill color is yellow. You can provide any color you want. I simply created colors to provide contrast against black background. 

Then comes the physics body. The Physics body gives movement to the node, and makes it behave in accordance with the gravity law. Collision is taken care of by the physics bodies. 

Create a rectangular physics body of the same size as that of the rectangular node. Then I made sure the physics body is not affected by gravity because, I want them to be affected only by the two Field bodies' gravitational forces. 

Let the physics body have a mass of 5.  Make sure dynamic is set to true, so that they can move with respect to the forces acting on them. 

Set the physics Body of the rectangular SKNode to the newly created physics body. Then add the node to the scene. Scene is the parent of the node. 

Similarly create circle node :
        let circleNode = SKShapeNode(circleOfRadius: 20);
        circleNode.position.x = 500;
        circleNode.position.y = 300
        circleNode.strokeColor = SKColor.greenColor()
        circleNode.fillColor = SKColor.orangeColor()
        let circleBody = SKPhysicsBody(circleOfRadius: 20)
        circleBody.affectedByGravity = rectBody.affectedByGravity
        circleBody.mass = 10
        circleBody.dynamic = true //should be dynamic
        circleNode.physicsBody = circleBody

        self.addChild(circleNode)

And create the anchor node:
  
        let AnchorNode = SKShapeNode(circleOfRadius: 25);
        AnchorNode.position.x = circleNode.position.x
        AnchorNode.position.y = circleNode.position.y + 250
        AnchorNode.alpha = 0
        AnchorNode.strokeColor = SKColor.greenColor()
        AnchorNode.fillColor = SKColor.redColor()
        let AnchorBody = SKPhysicsBody(circleOfRadius: 20)
        AnchorBody.affectedByGravity = false
        AnchorBody.dynamic = false //should be dynamic
        
        AnchorNode.physicsBody = AnchorBody
        AnchorBody.affectedByGravity = false
        self.addChild(AnchorNode)

The important points to note in the anchor node are as follows: Anchor node is invisible because I want the rod to look closer to a real pendulum. Position is same as that of the other end of the rod. It also has a body but it is not affected by gravity. It is not dynamic as well. It should stay put like a nail on the wall. It needs a physics body, in order to create a Pin joint later with the rod. Red circle is the anchor, orange is the pendulum bob.



For the sake of clarity, the red circle has an alpha of 1, and is visible. But it will be rendered transparent in the final code. 

Now that the three major nodes are created, I am going to create two Field Nodes which would act as radial gravity nodes, which means, objects would be attracted towards them and would follow a natural curve of moving towards them and moving away from them. 

I tried to create pendulum with one such node. but It did not give expected results, hence I am falling back to two nodes. 




The cyan colored circles are the Field nodes which would try to pull the bob towards them. Note: SKFieldNode is available only for iOS 8.0 and above. 

Here is the code snippet for them. 


        let GravityNode = SKFieldNode.radialGravityField()
        GravityNode.position.x = 350
        GravityNode.position.y = 290
        GravityNode.strength = 6
        GravityNode.falloff = 3.5
        

        self.addChild(GravityNode)

lAs we already know, let keyword creates a constant reference to the object, meaning, the GravityNode cannot point to any other object. The properties inside the object are editable, but not the GravityNode. 

Set the position of one of the radial gravity node on the left side of the pendulum. Set its strength to be 6, and falloff to be 3.5 . Strength is the pull strength of its gravity. FallOff is the variable which sets the exponent value which determines how much of gravitational pull the objects would feel, after a certain distance. 

Strength and falloff had to be tweaked in order to get a smooth swinging pendulum. 

Add the node to the scene. 

        let GCircleNode = SKShapeNode(circleOfRadius: 25);
        GCircleNode.position = GravityNode.position
        GCircleNode.strokeColor = SKColor.greenColor()
        GCircleNode.fillColor = SKColor.cyanColor()
        GCircleNode.alpha = 1
        self.addChild(GCircleNode)

This is a debug node which was created to show the position of the field node. This is not required to be rendered for the final run. 

Similarly create a second field node, to pull the pendulum towards the other direction. 

        let GravityNode1 = SKFieldNode.radialGravityField()
        GravityNode1.position.x = 650
        GravityNode1.position.y = GravityNode.position.y
        GravityNode1.strength = GravityNode.strength
        GravityNode1.falloff = GravityNode.falloff
        self.addChild(GravityNode1)

        
        let GCircleNode1 = SKShapeNode(circleOfRadius: 25);
        GCircleNode1.position = GravityNode1.position
        GCircleNode1.strokeColor = SKColor.greenColor()
        GCircleNode1.fillColor = SKColor.cyanColor()
        GCircleNode1.alpha = 1
        self.addChild(GCircleNode1)

Now the rod needs to be pinned to the invisible anchor node, and the bob circle needs to be pinned to the other end of the rod, in order to move in alignment  with the rod. 

        let PinJoint = SKPhysicsJointPin.jointWithBodyA(circleNode.physicsBody, bodyB: 
          rectNode.physicsBody, anchor: circleNode.position)
        
        let stickJoint = SKPhysicsJointPin.jointWithBodyA(rectNode.physicsBody, bodyB: 
          AnchorNode.physicsBody, anchor: AnchorNode.position)
        
        self.physicsWorld.addJoint(PinJoint)
        self.physicsWorld.addJoint(stickJoint)

The physics joint Pin, fixes bodyA to Body B at a certain point. This point is called anchor point. Body A is circle (bob) and body B is the rod in the first PinJoint. They are fixed at the bob's position. 

Second stickJoint(for lack of better term) joins the rod and the invisible anchor node at the anchor node's position. 

Add these two joints to the world. Now since the anchor's dynamic property is set to false, and it is not affected by gravity, It would stay put, and the rod has no other option than to stay put at that end. But the other end can move freely, and the bob circle will move whereever the other end moves because it is pinned to the rod at that point. 

Now we have a working pendulum, whose bob is being pulled equally by left and right gravity nodes. When the bob reaches the center after being pulled by right node, it will be pulled by the left one, thereby swinging from one end to the other. 




Code Jam for the week of Mar 15 2015
Project uploaded here : https://github.com/swtsvn/CJAW/tree/master/PhysicsGame

Sunday, March 8, 2015

Code Jam : Swift : Gradient and Graphics

Today I am going to enter graphics in swift, or at least the basics of it. 
In a simple view controller, ( I assume you know how to create a view controller by now, if not do refer to my previous blogs), Create a view. A view is nothing but a sub window inside a view controller. It is a subclass of UIView, and can be used to draw elements on screen. 

This week, I am going to create a bezier path with transformations, and color it with gradient color. This would be the simplest of all the graphics we can do in swift, but this is where it starts. 

In viewController.swift (or the file name you have for the main view controller), create a new class called MyView (or any other name you prefer), and subclass it to UIView.

class MyView : UIView{

Override the function drawRect().

override func drawRect(rect: CGRect) {

1. Transformations : I assume the readers know about graphics transformations. If not here are few good pages on it (http://www.cs.uic.edu/~jbell/CourseNotes/ComputerGraphics/2DTransforms.html and http://www.cs.utexas.edu/~fussell/courses/cs384g-fall2010/lectures/lecture07-Affine.pdf)
In this week, I am going to add rotation, and translation to the bezier curve. Rotation is created by Core Graphics function  CGAffineTransformMakeRotation.  cThis function creates a rotation matrix from unit matrix. The argument is angle in radians.CGAffineTransformMakeRotation is one of the methods to rotate objects on screen. The other way is to directly use CGContextRotateCTM, which rotates within a context. For this week, we will see affine transformation matrix. Eventually I will end up using context to append the transformation matrices.

 var rot = CGAffineTransformMakeRotation(CGFloat(M_PI/ 4)

The angle of rotation is 45 degrees ( Pi/4).  Then I translate the object to bring it inside the view. 


var trans = CGAffineTransformMakeTranslation(100, -150)

This creates another matrix that would translate the object by 100 units along x axis and -150 units along y axis. Since iOS coordinate system origin is at top left corner of screen, this would move the object towards right and up.

Next I am saving the current context, this is to make sure, we can restore back to the previous context after our customized changes to the current transformation matrix (CTM)

 let context = UIGraphicsGetCurrentContext()
 CGContextSaveGState(context)
UIGraphicsGetCurrentContext This function returns back a  CGContextRef of the current context ( https://developer.apple.com/library/ios/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html ) being used. Current context is nil by default. the UIView object (MyView class object in our case) pushes a valid context into the stack, making it current, to draw objects, and this happens just before calling drawRect() method. In case you are not using UIView object, then you must manually push context using UIGraphicsPushContext method. 

 CGContextConcatCTM(context, rot)
 CGContextConcatCTM(context, trans)

These lines concatenates rotation matrix, and translation matrix to the existing matrix of the context. The order is important. These lines would first rotate the object then translate. 

2. Bezier Curve : Next is to draw a bezier curve (  http://web.iitd.ac.in/~hegde/cad/lecture/L13_Bezi ercurve.pdf ) . To create the curve programmatically. I am creating a rectangle with a circle at its bottom to look similar to a pendulum but not exactly one. This would include two bezier paths inside one. 

First create the rectangle path. 
  var bezierPath = UIBezierPath();
  let r = CGRectInset(rect, rect.size.width * 0.475, rect.size.height * 0.05)
  var rpath = UIBezierPath(roundedRect: r, cornerRadius: 60.0)
      
Create a new UIBezierPath, then create a rounded rectangle by using CGRectInset with rectangle as the view’s rectangle itself, and the roundness to reduce the width by a lot, but height by a small fraction, so that a long slender rectangle is created. Then create a bezier path using the provided easy API UIBezierPath instead of creating your own control points using  addLineToPoint or addCurveToPoint functions. 

Then create another bezier path that would create a circle. 
let newrect = CGRectMake(20, 105, 250  , 250)
let h = CGRectInset(newrect, newrect.size.width * 0.3, newrect.size.width * 0.3)
var headpath = UIBezierPath(ovalInRect: h);
let rad = 20.0
        
This creates another rectangle that would position the circle at the bottom part of the rectangle that I already created. CGRectInset is used to create circle with this rectangle. UIBezierPath is used to create bezier path. using ovalInRect parameter. 

 rpath.appendPath(headpath)
 bezierPath.appendPath(rpath);
 bezierPath.addClip()

Add headpath to rpath ( circle to rectangle), then append that combined path to the main empty bezierPath. Then do addClip(). This method intersects the shape with current clipping region of the graphics context. This method is important to create the gradient color filling to the path. 

3. Gradient filling:

 let colorspace = CGColorSpaceCreateDeviceRGB()
 let g1 = UIColor(red: 0.5, green: 0.2, blue: 0.8, alpha: 1)
 let g2 = UIColor(red: 1, green: 0.6, blue: 0.2, alpha: 1
 let gc : CFArray = [g1.CGColor, g2.CGColor, UIColor.blueColor().CGColor, UIColor.redColor().CGColor]
 let gl : [CGFloat] = [0, 0.3,0.5,1]
 let gra = CGGradientCreateWithColors(colorspace, gc, gl)
       
 CGContextDrawLinearGradient(context, gra, CGPointMake(self.bounds.width/2, self.bounds.height - 20), CGPointMake(self.bounds.width / 2, 20), 0)

First create device dependent RGB color space by using CGColorSpaceCreateDeviceRGB . This is important to set the colors for the gradient. 
Create two colors g1 and g2, and setup 4 colors for the gradient to change from. Then create color array using 4 colors (g1, g2, blue, red). Create coordinates array, which specifies where the color should change. This is again an array of 4 elements. 
Then create gradient with colors and coordinates. using CGGradientCreateWithColors . Then draw gradient colors. Since we already clipped using addClip, the gradient colors will be applied to the bezier path. 

 CGContextDrawLinearGradient(context, gra, CGPointMake(self.bounds.width/2, self.bounds.height - 20), CGPointMake(self.bounds.width / 2, 20), 0)
       
 CGContextRestoreGState(context)

CGContextDrawLinearGradient is used to draw the gradient colors to the bezier shapes.  The third and fourth parameters are CGPoints that define the coordinates for starting and ending points.  CGContextRestoreGState is used to restore the context that was used before we saved it and changed it. 
For a simple project like this one, saving and restoring might not be necessary. But it is required for complex ones with multiple objects to be rendered on the screen. 

The final rendered view looks like this :

The viewController.swift code :


import UIKit

class MyView : UIView{
   
override func drawRect(rect: CGRect) {
       
       
       
       
// 1. transformations
        var rot = CGAffineTransformMakeRotation(CGFloat(M_PI) / 4)
       
var trans = CGAffineTransformMakeTranslation(100, -150)
       
       
let context = UIGraphicsGetCurrentContext()
       
CGContextSaveGState(context)
       
CGContextConcatCTM(context, rot)
       
CGContextConcatCTM(context, trans)
       
   
       
//bezier path
        var bezierPath = UIBezierPath();
       
       
let r = CGRectInset(rect, rect.size.width * 0.475, rect.size.height * 0.05)
       
var rpath = UIBezierPath(roundedRect: r, cornerRadius: 60.0)
      
       
       
let newrect = CGRectMake(20, 105, 250  , 250)
       
let h = CGRectInset(newrect, newrect.size.width * 0.3, newrect.size.width * 0.3)
       
var headpath = UIBezierPath(ovalInRect: h);
       
let rad = 20.0
     
        rpath.
appendPath(headpath)
   
       
        bezierPath.
appendPath(rpath);
        bezierPath.
addClip()
      
       
//gradient
        let colorspace = CGColorSpaceCreateDeviceRGB()
       
let g1 = UIColor(red: 0.5, green: 0.2, blue: 0.8, alpha: 1)
       
let g2 = UIColor(red: 1, green: 0.6, blue: 0.2, alpha: 1)
       
let gc : CFArray = [g1.CGColor, g2.CGColor, UIColor.blueColor().CGColor, UIColor.redColor().CGColor]
       
let gl : [CGFloat] = [0, 0.3,0.5,1]
       
       
let gra = CGGradientCreateWithColors(colorspace, gc, gl)
       
       
CGContextDrawLinearGradient(context, gra, CGPointMake(self.bounds.width/2, self.bounds.height - 20), CGPointMake(self.bounds.width / 2, 20), 0)
       
       
CGContextRestoreGState(context)
    
    }
}


class MyViewController: UIViewController{
  
   
   
let myview = MyView(frame: CGRectMake( 10, 100, 300, 300))
   
@IBOutlet var colorLabel: UILabel!
   
override func viewDidLoad() {
       
super.viewDidLoad()
       
// Do any additional setup after loading the view, typically from a nib.
        view.addSubview(myview);
    }

   
override func didReceiveMemoryWarning() {
       
super.didReceiveMemoryWarning()
       
// Dispose of any resources that can be recreated.
    }
}




Error & Solution : If you get a blank screen instead of the main view controller, and an error in the debug window stating "perhaps the designated entry point is not set?”, then it means there is no initial view controller for the story board. Select the storyboard->then the navigation controller or the view controller that you want as the first screen -> attributes inspector -> check the “Set as initial view controller” option. It should be enabled.

Code Jam for the week of Mar 1 2015