radialGravityField goes nuts after a while - macos

I'm trying to create a SpriteKit game using Swift that looks somewhat like a 2D gravity simulator, but when my objects approach a bit they go to the opposite direction with crazy speeds.
This may be related with the minimumRadius, categoryBitMask or fieldBitMask properties on the field and physicsBody, but I don't know exactly.
Here's the relevant code:
override func mouseDown(theEvent: NSEvent) {
let location = theEvent.locationInNode(self)
let sprite = SKShapeNode(circleOfRadius: 5)
sprite.fillColor = SKColor(red: 0, green: 0.5, blue: 1, alpha: 1)
sprite.strokeColor = SKColor.clearColor()
sprite.position = location
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 5)
let gravityField = SKFieldNode.radialGravityField()
//gravityField.minimumRadius = 5
sprite.addChild(gravityField)
self.addChild(sprite)
}
And a gif demonstration of the problem:
As you can see, the distance that the objects need to move to trigger the issue seems to be random.

Related

How to rotate UIBezierPath around center of its own bounds?

Lets say we have a UIBezierPath... the bounds of which are perfectly square... like this:
func getExponentPath(rotate180: Bool) -> UIBezierPath {
// establish unit of measure (grid) based on this containing view's bounds... (not to be confused with this bezierpath's bounds)
let G = bounds.width / 5
let exponentPath = UIBezierPath()
let sstartPoint = CGPoint(x:(3.8)*G,y:(1.2)*G)
exponentPath.move(to: sstartPoint)
exponentPath.addLine(to: CGPoint(x:(5)*G,y:(1.2)*G))
exponentPath.addLine(to: CGPoint(x:(4.4)*G,y:(0.2)*G))
exponentPath.addLine(to: CGPoint(x:(5)*G,y:(0.2)*G))
exponentPath.addLine(to: CGPoint(x:(5)*G,y:(0)*G))
exponentPath.addLine(to: CGPoint(x:(3.8)*G,y:(0)*G))
exponentPath.addLine(to: CGPoint(x:(3.8)*G,y:(0.2)*G))
exponentPath.addLine(to: CGPoint(x:(4.4)*G,y:(0.2)*G))
exponentPath.addLine(to: sstartPoint)
exponentPath.close()
// this does not work:
// if rotate180 { exponentPath.apply(CGAffineTransform(rotationAngle: CGFloat.pi)) }
return exponentPath
}
If rotated, this bezierpath still needs to occupy the exact same area within its containing view.
I can only presume this does not work because there's some problem with the center of rotation not being what I intend... although I get the same (wrong) result even when saying "rotate by 0."
So how can the path be rotated around it's own center point?
It seems like there should be a simple linear algebra matrix multiplication type thingy that could be applied to the set of points. =T
extension UIBezierPath
{
func rotateAroundCenter(angle: CGFloat)
{
let center = self.bounds.getCenter()
var transform = CGAffineTransform.identity
transform = transform.translatedBy(x: center.x, y: center.y)
transform = transform.rotated(by: angle)
transform = transform.translatedBy(x: -center.x, y: -center.y)
self.apply(transform)
}
}
I don't think you need the rotation. To draw the same shape upside down, just flip it:
exponentPath.apply(CGAffineTransform(scaleX: 1, y: -1))
exponentPath.apply(CGAffineTransform(translationX: 0, y: G))
So in case anyone else is trying to rotate a UIBezierPath on the center of it's own bounding rectangle... this is the actual working solution arrived at with help from previous answers/comments:
func getExponentPath(rotationAngle: CGFloat) -> UIBezierPath {
// ...
let x_translation = -( (bounds.width) - ( exponentPath.bounds.width/2) )
let y_translation = -exponentPath.bounds.height/2
exponentPath.apply(CGAffineTransform(translationX: x_translation, y: y_translation))
exponentPath.apply(CGAffineTransform(rotationAngle: rotationAngle))
exponentPath.apply(CGAffineTransform(translationX: -x_translation, y: -y_translation))
// ...
}

SceneKit shows only partly a large rotated SCNPlane

I try to create a large SCNPlane to cover whole screen. The test code is bellow in which a red box (size 1x1x1) is in the middle of a blue plane (size 200 x200). They all are in the central point (0, 0, 0) and the camera is only +5 from that point.
When the plane node faces to the camera (with a large angle), it works well (figure 1) and both left and right sides could cover whole left and right sides of the screen. However when I rotate the plane to a small angle (with the camera), only a small part is shown. In figure 2, the left side of the plane comes closer to the camera. That left side should be wide enough (side of 100) to cover all left side of the screen but it is not. Increasing the size of the plane to 10 times (to 2000) did not help.
Any idea about the problem and solution? Thanks
override func viewDidLoad() {
super.viewDidLoad()
let scnView = self.view as! SCNView
scnView.backgroundColor = UIColor.darkGray
scnView.autoenablesDefaultLighting = true
scnView.allowsCameraControl = true
scnView.scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scnView.scene?.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
let theBox = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)
theBox.firstMaterial?.diffuse.contents = UIColor.red
let theBoxNode = SCNNode(geometry: theBox)
theBoxNode.position = SCNVector3(0, 0, 0)
scnView.scene?.rootNode.addChildNode(theBoxNode)
let plane = SCNPlane(width: 200, height: 200)
plane.firstMaterial?.diffuse.contents = UIColor.blue
let planeNode = SCNNode(geometry: plane)
scnView.scene?.rootNode.addChildNode(planeNode)
}
you might want to check your camera's zNear property to ensure that the plane isn't clipped. You can find an explanation of clipping planes here.

Rotate CIImage in Swift 2 using CGAffineTransformMakeRotation

I try and rotate a CIImage in Swift2 using
let rotatedImage = someCIImage.imageByApplyingTransform(CGAffineTransformMakeRotation(CGFloat(M_PI / 2.0))))
When I look at the sized of the resulting rectangle, it has been rotated. (it was 1000x500 and now is 500x1000). However, the calculations I do subsequently (convert to bitmap and access individual pixels) indicate differently. Am I right that the the above transformation rotates around the center of the image, i.e. in the above example around 500/250?
That transform rotates around the image's origin. This version sets the pivot point to the centre:
var tx = CGAffineTransformMakeTranslation(
image.extent.width / 2,
image.extent.height / 2)
tx = CGAffineTransformRotate(
tx,
CGFloat(M_PI_2))
tx = CGAffineTransformTranslate(
tx,
-image.extent.width / 2,
-image.extent.height / 2)
var transformImage = CIFilter(
name: "CIAffineTransform",
withInputParameters: [
kCIInputImageKey: image,
kCIInputTransformKey: NSValue(CGAffineTransform: tx)])!.outputImage!
Simon
In Swift 5, the code gets nicer. This is a CIImage extension method to easily rotate itself around the center.
func rotate(_ angle: CGFloat) -> CIImage {
let transform = CGAffineTransform(translationX: extent.midX, y: extent.midY)
.rotated(by: angle)
.translatedBy(x: -extent.midX, y: -extent.midY)
return applyingFilter("CIAffineTransform", parameters: [kCIInputTransformKey: transform])
}

during Spritenode animation and movement appear red large x

Here is problem in which I add zoombie sprite to the scene every one second. When I add another sub animated zoombie to the zoombie node, sometimes it loads animated texture, and other times appear red large X.
func addMonster() {
let zoombieSprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(40, 60))
// Determine where to spawn the monster along the Y axis
let actualY = randRange(lower: zoombieSprite.size.height, upper: size.height - zoombieSprite.size.height)
// Position the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
zoombieSprite.position = CGPoint(x: size.width + zoombieSprite.size.width/2, y: actualY)
zoombieSprite.physicsBody = SKPhysicsBody(rectangleOfSize: zoombieSprite.size) // 1
zoombieSprite.physicsBody?.dynamic = true // 2
zoombieSprite.physicsBody?.categoryBitMask = PhysicsCategory.Monster // 3
zoombieSprite.physicsBody?.contactTestBitMask = PhysicsCategory.Projectile // 4
zoombieSprite.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
addChild(zoombieSprite)
//zoombieSprite.addChild(createAnimatedZoombie())
let zoombieAnimation = SKAction.runBlock({
zoombieSprite.addChild(self.createAnimatedZoombie())
})
// Determine speed of the monster
let actualDuration = randRange(lower: 6.0, upper: 10.0)
//print("actualDuration = \(actualDuration)")
let actionMove = SKAction.moveTo(CGPoint(x: -zoombieSprite.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
// Create the actions
let actionMoveDone = SKAction.removeFromParent()
zoombieSprite.runAction(SKAction.sequence([zoombieAnimation ,actionMove,actionMoveDone]))
}
//MARK: - ANIMATE FRAME AND MOVE ZOOMBIE
func createAnimatedZoombie () -> SKSpriteNode {
let animatedZoobieNode = SKSpriteNode(texture: spriteArray[0])
let animationFrameAction = SKAction.animateWithTextures(spriteArray, timePerFrame: 0.2)
let durationTime = SKAction.waitForDuration(0.1)
let repeatAction = SKAction.repeatActionForever(animationFrameAction)
let quenceAction = SKAction.sequence([durationTime, repeatAction])
animatedZoobieNode.runAction(quenceAction)
return animatedZoobieNode
}
Thanks very much my respectable brother Joseph Lord and Thank God i solved my problem by just dividing sprite kit atlas array count property by 2 because in this folder i had put both #2x and #3x images so when i used to get number of images from this atlas folder it used to return the number which was addition of #2x and #3x images.

How to make two lines in UIBezierPath to animate at different speed

Say the following scenario:
I have drawn a quadrilateral shape, which is a mask for a UIView. I denote the shape layer as maskLayer. maskLayer crops the bottom of the UIView asymmetrically.
But then I want to fully reveal my UIView in an animation. The animation should be left side of maskLayer drops down to the bottom of UIView, and .2 sec later my right side of maskLayer also drops down to the bottom of UIView, thus fully reveal the entity of UIView.
My approach is to drop down left line first, then right one as the following code:
//this quadrilateral will put down left corner to the bottom of screen
var path2 = UIBezierPath()
path2.moveToPoint(CGPointZero)
path2.addLineToPoint(CGPoint(x: 0, y: frame.height))
path2.addLineToPoint(CGPoint(x: frame.width, y: frame.height / goldRatio / goldRatio))
path2.addLineToPoint(CGPoint(x: frame.width, y: 0))
path2.closePath()
//this rectangle path will put down both corner to the bottom of screen
//thus fix the view to its original shape
var path3 = UIBezierPath()
path3.moveToPoint(CGPointZero)
path3.addLineToPoint(CGPoint(x: 0, y: frame.height))
path3.addLineToPoint(CGPoint(x: frame.width, y: frame.height))
path3.addLineToPoint(CGPoint(x: frame.width, y: 0))
path3.closePath()
I have spent 2 hours trying to figure it out to no avail. May you please give me some instructions about how to achieve just that.
The initial state is like the following:
The end state is like the following:
I truly appreciate your help!
Easiest way to do this... Cheat.
Don't try to animate the path of the shape it really doesn't need it.
What you should do is something like this.
Create your final state view. Rectangular, at the bottom of the screen with the UI on it etc...
This final state view will not move. It will always be here.
Now create another view and insert it as a sub view underneath the final state view. On this you can add a shape layer with the angular corner cut off.
Now all you need to do is animate the position of this angular view downward until it is completely below the final state view.
If they are the same colour the this will give the effect of animating the path of the shape.
To get the different speeds you could have a rectangluar shape layer rotated to 45 degrees. Then animate it to 0 degrees as the view slides down?
In fact, you could do this with a single shape layer that is rotated and moved.
To do this sort of animation, you would generally use a CADisplayLink (sort of like a timer, but linked to updates of the display rather than some arbitrary interval) to repeatedly change the path associated with desired shape. I think this is easiest if you use a CAShapeLayer and just change the path property of this shape.
To make this work, you need a function that represents the path at a given point of time (or easier, a path at an instant a certain percentageComplete along the animation duration). Since you have a regular shape (constantly the same number of points), you can simply interpolate between some array of startPoints and endPoints.
So, create the shape layer, capture the start time, start the display link, and then for every "tick" of the display link, calculate what percentage of the total animationDuration has passed, and update the shape layer's path accordingly:
class ViewController: UIViewController {
let animationDuration = 2.0
var displayLink: CADisplayLink!
var startTime: CFAbsoluteTime!
var shapeLayer: CAShapeLayer!
let goldRatio: CGFloat = 1.6180339887
var startPoints:[CGPoint]!
var endPoints:[CGPoint]!
override func viewDidLoad() {
super.viewDidLoad()
self.startAnimation()
}
func startAnimation() {
startPoints = [
CGPoint(x: 0, y: view.bounds.size.height),
CGPoint(x: 0, y: view.bounds.size.height - view.bounds.size.height / goldRatio),
CGPoint(x: view.bounds.size.width, y: 0),
CGPoint(x: view.bounds.size.width, y: view.bounds.size.height)
]
endPoints = [
CGPoint(x: 0, y: view.bounds.size.height),
CGPoint(x: 0, y: view.bounds.size.height * 0.75),
CGPoint(x: view.bounds.size.width, y: view.bounds.size.height * 0.75),
CGPoint(x: view.bounds.size.width, y: view.bounds.size.height)
]
assert(startPoints.count == endPoints.count, "Point counts don't match")
createShape()
startDisplayLink()
}
func startDisplayLink() {
displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
startTime = CFAbsoluteTimeGetCurrent()
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
func stopDisplayLink() {
displayLink.invalidate()
displayLink = nil
}
func handleDisplayLink(displayLink: CADisplayLink) {
var percent = CGFloat((CFAbsoluteTimeGetCurrent() - startTime) / animationDuration)
if percent >= 1.0 {
percent = 1.0
stopDisplayLink()
}
updatePathBasedUponPercentComplete(percent)
}
func createShape() {
shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.blueColor().CGColor
shapeLayer.strokeColor = UIColor.clearColor().CGColor
updatePathBasedUponPercentComplete(0.0)
view.layer.addSublayer(shapeLayer)
}
func updatePathBasedUponPercentComplete(percentComplete: CGFloat) {
shapeLayer.path = pathBasedUponPercentComplete(percentComplete, frame: view.frame).CGPath
}
func pathBasedUponPercentComplete(percentComplete: CGFloat, frame: CGRect) -> UIBezierPath {
var path = UIBezierPath()
for i in 0 ..< startPoints.count {
let point = CGPoint(
x: startPoints[i].x + (endPoints[i].x - startPoints[i].x) * percentComplete,
y: startPoints[i].y + (endPoints[i].y - startPoints[i].y) * percentComplete
)
if i == 0 {
path.moveToPoint(point)
} else {
path.addLineToPoint(point)
}
}
path.closePath()
return path
}
}

Resources