How to make UIBezierPath smooth in Swift - swift

I am trying to create a line chart which represents a set of values (x and y) in a smooth bezier curve. This works fine, except when the x-values are close to each other and the y-values go from a continuous line to a lower or higher value. The values are not shown in the chart itself, but here is an image illustrating my problem:
As you can see, the line makes a backwards movement before continuing to the next point. I would like this to not happen and smoothen out. To generate the data points, I use this library from Minh Nguyen, which has helped me a lot. The only problem is this issue still. For easiness, here is the code I currently use:
private func controlPointsFrom(points: [CGPoint]) -> [CurvedSegment] {
var result: [CurvedSegment] = []
let delta: CGFloat = 0.3
for i in 1..<points.count {
let A = points[i-1]
let B = points[i]
let controlPoint1 = CGPoint(x: A.x + delta*(B.x-A.x), y: A.y + delta*(B.y - A.y))
let controlPoint2 = CGPoint(x: B.x - delta*(B.x-A.x), y: B.y - delta*(B.y - A.y))
let curvedSegment = CurvedSegment(controlPoint1: controlPoint1, controlPoint2: controlPoint2)
result.append(curvedSegment)
}
for i in 1..<points.count-1 {
let M = result[i-1].controlPoint2
let N = result[i].controlPoint1
let A = points[i]
let MM = CGPoint(x: 2 * A.x - M.x, y: 2 * A.y - M.y)
let NN = CGPoint(x: 2 * A.x - N.x, y: 2 * A.y - N.y)
result[i].controlPoint1 = CGPoint(x: (MM.x + N.x)/2, y: (MM.y + N.y)/2)
result[i-1].controlPoint2 = CGPoint(x: (NN.x + M.x)/2, y: (NN.y + M.y)/2)
}
return result
}
func createCurvedPath(_ dataPoints: [CGPoint]) -> UIBezierPath? {
let path = UIBezierPath()
path.move(to: dataPoints[0])
var curveSegments: [CurvedSegment] = []
let useDataPoints = dataPoints.filter { ($0.y < 1000) }
curveSegments = controlPointsFrom(points: useDataPoints)
for i in 1..<useDataPoints.count {
path.addCurve(to: useDataPoints[i], controlPoint1: curveSegments[i - 1].controlPoint1, controlPoint2: curveSegments[i - 1].controlPoint2)
}
return path
}
For documentation, I would refer to the tutorial/blogpost I linked earlier. I figure the issue should be somewhere in the calculation of controlPoint1 and controlPoint2 in the controlPointsFrom function. When I remove the delta or make it 0, it just become straight lines but then the issue doesn't occur either. So the math should be different I think, to keep track of the previous value and perhaps don't create a control point with a higher or lower y-value when the next point is lower or higher, respectively. But I am unable to figure out how to make it work. Any smart mind who can make this happen?
Would be forever grateful!

try this:
Smooth UIBezierPath
https://medium.com/#ramshandilya/draw-smooth-curves-through-a-set-of-points-in-ios-34f6d73c8f9

Related

SWIFT Syntax question with regards to a CLOSURE

maybe one could be so kind as to explain me this snippet
There is this nice tutorial about Core Graphics on raywenderlich. Unfortunately, the comments on that page are closed
The author declares
//Weekly sample data
var graphPoints = [4, 2, 6, 4, 5, 8, 3]
Note the "s" at the end of graphPoints. Then, to calculate the y coordinate for a chart with such figures, he uses graphPoint (without an "s" at the end) within a closure. Nevertheless the code runs just fine to my confusion.
// calculate the y point
let topBorder = Constants.topBorder
let bottomBorder = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints.max()!
let columnYPoint = { (graphPoint: Int) -> CGFloat in
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return graphHeight + topBorder - y // Flip the graph
}
And there is no further use of graphPoint in this project (that I am aware of, using "find"). So I wonder, how are graphPoints with an "s" linked to columnYPoint.
Though I currently have no idea how the y values flow into the closure, let me already extend my question: if my values are in a 2D array with the structure [[x1, x2], [y1, y2]], how would I pass only my y (or only my x) values into this closure?
Cheers!
UPDATE
This is how columnYPoint is used, afterwards, to draw the graph:
// draw the line graph
UIColor.white.setFill()
UIColor.white.setStroke()
// set up the points line
let graphPath = UIBezierPath()
// go to start of line
graphPath.move(to: CGPoint(x: columnXPoint(0), y: columnYPoint(graphPoints[0])))
// add points for each item in the graphPoints array
// at the correct (x, y) for the point
for i in 1..<graphPoints.count {
let nextPoint = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
graphPath.addLine(to: nextPoint)
}
graphPath.stroke()
As you have correctly identified, this is a closure (put into the variable called columnYPoint, giving it a name):
let columnYPoint = { (graphPoint: Int) -> CGFloat in
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return graphHeight + topBorder - y // Flip the graph
}
So really, it's like a function called columnYPoint:
func columnYPoint(_ graphPoint: Int) -> CGFloat {
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return graphHeight + topBorder - y // Flip the graph
}
Why did the author wrote a closure and put it into a variable, instead of writing a function? I have no idea, because I can't read minds. It's a stylistic choice by the author.
And if you look at how it is being called, this function/closure calculates the Y coordinate of the bar, given the height of the bar, graphPoint. graphPoint is the parameter of the function, so of course it is not used in the rest of the code. As you can see from the caller:
graphPath.move(to: CGPoint(x: columnXPoint(0), y: columnYPoint(graphPoints[0])))
// and
let nextPoint = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
columnYPoint will be called for each element in graphPoints, so graphPoint will be each value in graphPoints. We need to calculate the coordinates of every bar, after all.
There seems to also be a columnYPoint closure mentioned earlier, which calculates the X coordinate given a given bar index. You can combine these two closures to give you a single closure that gives you a single CGPoint:
let margin = Constants.margin
let graphWidth = width - margin * 2 - 4
let topBorder = Constants.topBorder
let bottomBorder = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints.max()!
let columnPoint = { (column: Int, graphPoint: Int) -> CGPoint in
//Calculate the gap between points
let spacing = graphWidth / CGFloat(self.graphPoints.count - 1)
let x = CGFloat(column) * spacing + margin + 2
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return CGPoint(x: x, y: graphHeight + topBorder - y) // Flip the graph
}

Identify the top left, top right, bottom left and bottom right position of a rectangle in Swift (elegant solution)

I'd like to find an elegant solution to identify the corners of a rectangle given a list of points (that I'm sure will define a rectangle).
Let's say we have this array of CGPoint:
var points:[CGPoint] = []
points.append(CGPoint(x:1, y:0)) //TL
points.append(CGPoint(x:3, y:0)) //TR
points.append(CGPoint(x:1, y:2)) //BL
points.append(CGPoint(x:3, y:2)) //BR
Which would be an elegant solution to understand that TopLeft corner is at index 0, Top Right at index 1... and so on?
I could cycle through the array multiple times and find it using a comparison... can you think at a better solution maybe using sort or filter ?
EDIT: Please note that the points array is unordered. I don't have a precise sequence of points.
These are Core Graphics structs, so ask Core Graphics to help you. Construct a path from any point through each of the other points in any order and ask for its bounding box. Now you have a CGRect whose corner points are your points, but now you know which is which, and matching them up to yours is trivial.
Example:
var points:[CGPoint] = []
points.append(CGPoint(x:1, y:0)) //TL
points.append(CGPoint(x:3, y:0)) //TR
points.append(CGPoint(x:1, y:2)) //BL
points.append(CGPoint(x:3, y:2)) //BR
let path = CGMutablePath()
path.move(to: points[0])
for ix in 1...3 {path.addLine(to: points[ix])}
let rect = path.boundingBox
The answer is CGRect(x:1.0, y:0.0, width:2.0, height:2.0) and now you know its minX, minY, maxX, and maxY and can easily match those up to your original points.
And you get the same result regardless of the order in which the points were supplied.
You can use map / reduce to achieve relatively simple syntax. Assuming:
var points: [CGPoint] = []
points.append(CGPoint(x: 3, y: 6))
points.append(CGPoint(x: 4, y: 6))
points.append(CGPoint(x: 4, y: 2))
points.append(CGPoint(x: 3, y: 2))
You can then:
let minX = points.map { $0.x } .reduce(points[0].x) { min($0,$1) }
At which point minX = 3. You could also use a sorted(by:)
let minX = points.sorted { $0.x < $1.x }.first!.x
Both have the advantages of working on any shape built from points. Matt also suggested using min() on the array, which looks like this:
let minX = points.min { $0.x < $1.x }!.x
I guess that's as clean as can be.
Oh, one more for the books... if you are going to be converting points to CGRect in a lot of places.. you can create yourself a CGRect extension:
extension CGRect {
init(from points: [CGPoint]) {
let xAxis = points.sorted { $0.x < $1.x }
let yAxis = points.sorted { $0.y < $1.y }
self.init(x: xAxis.first!.x, y: yAxis.first!.y, width: xAxis.last!.x - xAxis.first!.x, height: yAxis.last!.y - yAxis.first!.y)
}
}
Which you can then use with:
let rect = CGRect(from: points)
// rect.minX
Cheers!

Connect Physicsbodies on TileMap in SpriteKit

I use the following function to append physicsbodies on tiles from a SKTileMapNode:
static func addPhysicsBody(to tileMap: SKTileMapNode, and tileInfo: String){
let tileSize = tileMap.tileSize
let halfWidth = CGFloat(tileMap.numberOfColumns) / 2 * tileSize.width
let halfHeight = CGFloat(tileMap.numberOfRows) / 2 * tileSize.height
for row in 0..<tileMap.numberOfColumns{
for column in 0..<tileMap.numberOfRows{
let tileDefinition = tileMap.tileDefinition(atColumn: column, row: row)
let isCorrectTile = tileDefinition?.userData?[tileInfo] as? Bool
if isCorrectTile ?? false && tileInfo == "wall"{
let x = CGFloat(column) * tileSize.width - halfWidth
let y = CGFloat(row) * tileSize.height - halfHeight
let tileNode = SKNode()
tileNode.position = CGPoint(x: x, y: y)
tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2, y: tileSize.height / 2))
tileNode.physicsBody!.isDynamic = false
tileNode.physicsBody!.restitution = 0.0
tileNode.physicsBody!.categoryBitMask = Constants.PhysicsCategories.wall
tileNode.physicsBody!.collisionBitMask = Constants.PhysicsCategories.player | Constants.PhysicsCategories.npc | Constants.PhysicsCategories.enemy
nodesForGraph.append(tileNode)
tileMap.addChild(tileNode)
}
}
}
}
However if I use this, I have a physicsbody per tile. I want to connect physicsbodies to bigger ones to get a better performance. I know that this can be with init(bodies: [SKPhysicsBody]). But how can I do that?
How can I find out which body is next to another body to group them?
The physicsbodies in the tileMap aren't all next to each other. Some are big blocks of physicsbodies, some are single physicsbodies with no bodies next to them. So I can't simply put every physicsbody in an array and group them.
Here's an image that shows how it looks like at the moment.
I hope the explanation is clear enough. If not, I will try to explain it better.
Has anyone done this before and can point me in the right direction? I would appreciate any help.
EDIT:
Before I tried this:
static var bodies = [SKPhysicsBody]()
static func addPhysicsBody(to tileMap: SKTileMapNode, and tileInfo: String){
let tileSize = tileMap.tileSize
let halfWidth = CGFloat(tileMap.numberOfColumns) / 2 * tileSize.width
let halfHeight = CGFloat(tileMap.numberOfRows) / 2 * tileSize.height
for column in 0..<tileMap.numberOfColumns{
for row in 0..<tileMap.numberOfRows{
let tileDefinition = tileMap.tileDefinition(atColumn: column, row: row)
let isCorrectTile = tileDefinition?.userData?[tileInfo] as? Bool
if isCorrectTile ?? false && tileInfo == "wall"{
let x = CGFloat(column) * tileSize.width - halfWidth
let y = CGFloat(row) * tileSize.height - halfHeight
let tileNode = SKNode()
tileNode.position = CGPoint(x: x, y: y)
tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2, y: tileSize.height / 2))
tileNode.physicsBody!.isDynamic = false
tileNode.physicsBody!.restitution = 0.0
tileNode.physicsBody!.categoryBitMask = Constants.PhysicsCategories.wall
tileNode.physicsBody!.collisionBitMask = Constants.PhysicsCategories.player | Constants.PhysicsCategories.npc | Constants.PhysicsCategories.enemy
//nodesForGraph.append(tileNode)
bodies.append(tileNode.physicsBody!)
tileMap.addChild(tileNode)
}
}
}
tileMap.physicsBody = SKPhysicsBody(bodies: bodies)
}
But when I do this, the physicsbodies are totally messed up..
I recommend applying a line sweep algorithm to merge the tiles together.
You can do this in four steps;
Iterate through the position of the tiles in your SKTileMap.
Find the tiles that are adjacent to one another.
For each group of adjacent tiles, collect:
a down-left corner coordinate and
an up-right corner coordinate.
Draw a square, and move on to the next group of tiles until you run out of tile coordinates.
The first step: creating an array containing all of your position nodes.
func tilephysics() {
let tilesize = tileMap.tileSize
let halfwidth = CGFloat(tileMap.numberOfColumns) / 2.0 * tilesize.width
let halfheight = CGFloat(tileMap.numberOfRows) / 2.0 * tilesize.height
for col in 0 ..< tileMap.numberOfColumns {
for row in 0 ..< tileMap.numberOfRows {
if (tileMap.tileDefinition(atColumn: col, row: row)?.userData?.value(forKey: "ground") != nil) {
let tileDef = tileMap.tileDefinition(atColumn: col, row: row)!
let tile = SKSpriteNode()
let x = round(CGFloat(col) * tilesize.width - halfwidth + (tilesize.width / 2))
let y = round(CGFloat(row) * tilesize.height - halfheight + (tilesize.height / 2))
tile.position = CGPoint(x: x, y: y)
tile.size = CGSize(width: tileDef.size.width, height: tileDef.size.height)
tileArray.append(tile)
tilePositionArray.append(tile.position)
}
}
}
algorithm()
}
The second and third step: finding adjacent tiles, collecting the two corner coordinates, and adding them to an array:
var dir = [String]()
var pLoc = [CGPoint]()
var adT = [CGPoint]()
func algorithm(){
let width = tileMap.tileSize.width
let height = tileMap.tileSize.height
let rWidth = 0.5 * width
let rHeight = 0.5 * height
var ti:Int = 0
var ti2:Int = 0
var id:Int = 0
var dl:CGPoint = CGPoint(x: 0, y: 0)
var tLE = [CGPoint]()
var tRE = [CGPoint]()
for t in tilePositionArray {
if (ti-1 < 0) || (tilePositionArray[ti-1].y != tilePositionArray[ti].y - height) {
dl = CGPoint(x: t.x - rWidth, y: t.y - rHeight)
}
if (ti+1 > tilePositionArray.count-1) {
tLE.append(dl)
tRE.append(CGPoint(x: t.x + rWidth, y: t.y + rHeight))
} else if (tilePositionArray[ti+1].y != tilePositionArray[ti].y + height) {
if let _ = tRE.first(where: {
if $0 == CGPoint(x: t.x + rWidth - width, y: t.y + rHeight) {id = tRE.index(of: $0)!}
return $0 == CGPoint(x: t.x + rWidth - width, y: t.y + rHeight)}) {
if tLE[id].y == dl.y {
tRE[id] = CGPoint(x: t.x + rWidth, y: t.y + rHeight)
} else {
tLE.append(dl)
tRE.append(CGPoint(x: t.x + rWidth, y: t.y + rHeight))
}
} else {
tLE.append(dl)
tRE.append(CGPoint(x: t.x + rWidth, y: t.y + rHeight))
}
}
ti+=1
}
The fourth step: drawing a rectangle and moving on to the next shape:
for t in tLE {
let size = CGSize(width: abs(t.x - tRE[ti2].x), height: abs(t.y - tRE[ti2].y))
let loadnode = SKNode()
loadnode.physicsBody = SKPhysicsBody(rectangleOf: size)
loadnode.physicsBody?.isDynamic = false
loadnode.physicsBody?.affectedByGravity = false
loadnode.physicsBody?.restitution = 0
loadnode.physicsBody?.categoryBitMask = 2
loadnode.position.x = t.x + size.width / 2
loadnode.position.y = t.y + size.height / 2
scene.addChild(loadnode)
ti2 += 1
}
}
Apply these steps correctly, and you should see that your tiles are merged together in large squares; like so:
Screenshot without visuals for comparison
Screenshot without visuals showing the physicsbodies
I had a lot of fun solving this problem. If I have helped you, let me know.
I only recently started coding and am looking for new challenges. Please reach out to me if you have challenges or projects I could possibly contribute to.
As Knight0fDragon pointed out, there is no way to do exactly what you have asked. Unfortunately, tile maps in SpriteKit leave much to be desired. But you might try this technique to reduce the number of physics bodies.
Idea #1 - Manually Draw Your Physics Bodies
Create your tile map in the editor. Just paint your tile textures onto the map; don't assign any physics bodies to them. Then keep working in the editor to drag Color Sprites (SKSpriteNodes) over parts of your map that need a physics body. Shape the nodes to make the largest rectangle possible for areas that need physics bodies. This works best for for large, flat surfaces like walls, floors, ceilings, platforms, crates, etc. It's tedious but you end up with far fewer physics bodies in your simulation than if you automatically assign bodies to all tiles like you are doing.
Idea #2 - Use No Physics Bodies
This idea would probably require even more work, but you could potentially avoid using physics bodies altogether. First, create your tile map in the editor. Analyze your map to identify which tiles mark a barrier, beyond which the player should not cross. Assign a user data identifier to that type of tile. You would need different categories of identifiers for different types of barriers, and you may also need to design your artwork to fit this approach.
Once your barrier tiles are sufficiently identified, write code which checks the user data value for the tile currently occupied by the player sprite and restrict the sprite's movement accordingly. For example, if the player enters a title that marks an upper boundary, your movement code would not allow the player sprite to move up. Likewise, if the player enters a tile that marks the leftmost boundary, your movement code will not let the player travel left.
You can check out this related post where I basically suggest the same ideas. Unfortunately, SpriteKit's tile maps have no perfect solution for this problem.

Adding multiple nodes on different positions on Y axis

I am trying to add many nodes on different positions on Y axis. The problem is that for some reason the positions are always (0:0).
I've went through pretty much every SO question related to this but couldn't find answer.
I am generating random number between the maximum and minimum value:
func randomBetweenTwoNumbers(firstNumber: CGFloat, secondNumber: CGFloat) -> CGFloat{
return CGFloat(arc4random())/CGFloat(UINT32_MAX) * abs(firstNumber - secondNumber) + min(firstNumber, secondNumber)
}
Now I am trying to add them like this:
func addLeftSparks(){
let randomNumber = Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)
print(randomNumber)
let positions = [CGPoint(x: -275, y: randomNumber), CGPoint(x: -275, y: randomNumber)]
print(positions) // this is (0:0)
positions.enumerated().forEach { (index, point) in
let spriteNode = SKNode()
spriteNode.position = point
let sprite = SKSpriteNode(imageNamed: "Spark")
sprite.name = "Spark"
spriteNode.addChild(sprite)
}
}
The thing I am trying to achieve in picture:
Any help is appreciated, really.
You generated a random number, but you used it twice
let randomNumber = Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)
print(randomNumber)
let positions = [CGPoint(x: -275, y: randomNumber), CGPoint(x: -275, y: randomNumber)]
positions is 2 points with the same y, so you will only see one node. You need to call the random number function for each new random number you want.
(I know you said it was 0,0, but I don't think that's right, so I am ignoring that part -- if you really believe that, put more information in the print statement so that you know you aren't being fooled by some other output)

Spawning Nodes randomly between positions

I'm trying to make a matching game, where these circles appear randomly in a rectangular area, without overlapping. Here's the spawning function:
func SpawnRed(){
var Red = SKSpriteNode(imageNamed: "Matched_Red")
Red.size = CGSize(width: 50, height: 50)
Red.zPosition = 1
let MinValueX = self.size.width / 3 + 50
let MaxValueX = self.size.width / 1.5 - 50
let MinValueY = self.size.height / 1.5 + 25
let MaxValueY = self.size.height / 6
let SpawnPointX = UInt32(MaxValueX - MinValueX)
let SpawnPointY = UInt32(MaxValueY - MinValueY)
Red.position = CGPointMake(CGFloat(arc4random_uniform(SpawnPointX)) + MinValueY,CGFloat(arc4random_uniform(SpawnPointY)))
self.addChild(Red)
}
But for some reason, I keep getting
"Thread 1: EXC_BAD_INSTRUCTION(code=EXC_1386_INVOP,subcode=0x0)" error.
Can you find the solution? Also, it'll be so helpful if you tell me how to spawn nodes without overlapping.