Detect overlaping if enumerating nodes - swift

I would like to know how should I detect overlapping nodes while enumerating them? Or how should I make that every random generated position in Y axis is at least some points higher or lower.
This is what I do:
1 - Generate random number between -400 and 400
2 - Add those into array
3 - Enumerate and add nodes to scene with generated positions like this:
var leftPositions = [CGPoint]()
for _ in 0..<randRange(lower: 1, upper: 5){
leftPositions.append(CGPoint(x: -295, y: Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)))
}
leftPositions.enumerated().forEach { (index, point) in
let leftSparkNode = SKNode()
leftSparkNode.position = point
leftSparkNode.name = "LeftSparks"
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}
But like this sometimes they overlap each other because the generated CGPoint is too close to the previous one.
I am trying to add some amount of triangles to the wall and those triangles are rotated by 90°
To describe in image what I want to achieve:
And I want to avoid thing like this:

Your approach to this is not the best, i would suggest only storing the Y values in your position array and check against those values to make sure your nodes will not overlap. The following will insure no two sparks are within 100 points of each other. You may want to change that value depending on your node's actual height or use case.
Now, obviously if you end up adding too many sparks within an 800 point range, this just will not work and cause an endless loop.
var leftPositions = [Int]()
var yWouldOverlap = false
for _ in 0..<randRange(lower: 1, upper: 5){
//Moved the random number generator to a function
var newY = Int(randY())
//Start a loop based on the yWouldOverlap Bool
repeat{
yWouldOverlap = false
//Nested loop to range from +- 100 from the randomly generated Y
for p in newY - 100...newY + 100{
//If array already contains one of those values
if leftPosition.contains(p){
//Set the loop Bool to true, get a new random value, and break the nested for.
yWouldOverlap = true
newY = Int(randY())
break
}
}
}while(yWouldOverlap)
//If we're here, the array does not contain the new value +- 100, so add it and move on.
leftPositions.append(newY)
}
func randY() -> CGFloat{
return Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)
}
And here is a different version of your following code.
for (index,y) in leftPositions.enumerated() {
let leftSparkNode = SKNode()
leftSparkNode.position = CGPoint(x:-295,y:CGFloat(y))
leftSparkNode.name = "LeftSparks\(index)" //All node names should be unique
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}

Related

IOS Charts positioning of gridlines

I am struggling to find out how to set gridlines on my chart to specific points on the x axis and to centre labels on the lines corresponding to the time of day. I have a simple chart, the data represents a day with about 280 data points (one for every 5 minute period). I want to show gridlines at times of the day equal to 03:00, 06:00, 12:00, 15:00, 18:00 and 21:00 and show the corresponding labels centred below the gridlines. I have have also tried LimitLines, which works for placing the vertical lines at the correct points, but there does not seem to be the ability to align the time labels with the centre of the limit lines.
I know this can be done because I have seen screenshots on-line showing time labels centred on gridlines (or limitlines) for specific periods on the day, but I've spent hours looking for how to do this an I'm drawing a blank.
My code below (note the commented out failed attempt to get limitlines to work).
let tideHeights = LineChartDataSet(entries: dataEntries)
tideHeights.drawCirclesEnabled = false
tideHeights.colors = [NSUIColor.black]
//let timings = ["00:00", "03:00", "06:00", "12:00", "15:00", "18:00", "21:00"]
let data = LineChartData(dataSet: tideHeights)
lineChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: dataPoints)
//lineChartView.xAxis.drawGridLinesEnabled = false
//let gridLine = values.count / timings.count
//var xPos = 0
//for label in timings {
//let limitLine = ChartLimitLine(limit: Double(xPos), label: label)
//limitLine.labelPosition = .bottomLeft
//limitLine.lineWidth = 0.5
//limitLine.lineColor = .black
//limitLine.valueTextColor = .black
//lineChartView.xAxis.addLimitLine(limitLine)
//xPos += gridLine
//}
lineChartView.xAxis.labelCount = 7
lineChartView.xAxis.labelPosition = .bottom
lineChartView.xAxis.granularity = 1
lineChartView.xAxis.avoidFirstLastClippingEnabled = true
lineChartView.legend.enabled = false
lineChartView.data = data
The output is below, the gridlines are at system set positions, how can I control where they are placed?
Update:
After applying the very helpful suggestion by Stephan Schlecht, I am still getting strange results. With lineChartView.xAxis.setLabelCount(9, force: true) I get:
and with lineChartView.xAxis.setLabelCount(4, force: true) I get:
It seems for some reason that the 1st gridline after the Axis is ignored.
Code is:
lineChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: dataPoints)
let marker = ChartMarker()
marker.setMinuteInterval(interval: 5)
marker.chartView = lineChartView
lineChartView.marker = marker
lineChartView.xAxis.setLabelCount(4, force: true)
lineChartView.xAxis.avoidFirstLastClippingEnabled = true
lineChartView.xAxis.labelPosition = .bottom
lineChartView.leftAxis.labelPosition = .insideChart
lineChartView.rightAxis.drawLabelsEnabled = false
lineChartView.legend.enabled = false
lineChartView.data = data
lineChartView.data?.highlightEnabled = true
Update 2
To reproduce, I have the following class which creates the data (btw, it is a stub class, eventually it will contain a paid api, so for now I generate the data for test purposes:
class TideHeights {
var tideHeights = [Double]()
var tideTimes = [Date]()
var strTimes = [String]()
var intervalMins = 5
init (intervalMins: Int) {
self.intervalMins = intervalMins
let slots = 24 * 60.0 / Double(intervalMins)
let seconds = 24 * 60 * 60.0 / slots
let radians = 2 * Double.pi / slots
let endDate = Date().endOfDay
var date = Date().startOfDay
var radianOffset = 0.0
repeat {
tideHeights.append(sin(radianOffset) + 1)
tideTimes.append(date)
let timeFormatter = DateFormatter()
timeFormatter.dateFormat = "HH:mm"
strTimes.append(timeFormatter.string(from: date))
date = date.advanced(by: seconds)
radianOffset += radians
} while date <= endDate
}
}
this class is instantiated and used to draw the graph in the following two functions in ViewController.
func drawChart() -> Void {
let tideData = TideHeights(intervalMins: 5)
lineChartView.dragEnabled = true
lineChartView.setScaleEnabled(false)
lineChartView.pinchZoomEnabled = false
setChartValues(dataPoints: tideData.strTimes, values: tideData.tideHeights)
}
func setChartValues(dataPoints: [String], values: [Double]) -> Void {
//initialise and load array of chart data entries for drawing
var dataEntries: [ChartDataEntry] = []
for i in 0..<values.count {
let dataEntry = ChartDataEntry(x: Double(i), y: values[i])
dataEntries.append(dataEntry)
}
let tideHeights = LineChartDataSet(entries: dataEntries)
tideHeights.drawCirclesEnabled = false
tideHeights.colors = [NSUIColor.black]
let gradientColors = [ChartColorTemplates.colorFromString("#72E700").cgColor,
ChartColorTemplates.colorFromString("#724BEB").cgColor]
let gradient = CGGradient(colorsSpace: nil, colors: gradientColors as CFArray, locations: nil)!
tideHeights.fillAlpha = 0.7
tideHeights.fill = Fill(linearGradient: gradient, angle: 90)
tideHeights.drawFilledEnabled = true
let data = LineChartData(dataSet: tideHeights)
lineChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: dataPoints)
let marker = ChartMarker()
marker.setMinuteInterval(interval: 5)
marker.chartView = lineChartView
lineChartView.marker = marker
lineChartView.xAxis.setLabelCount(4, force: true)
lineChartView.xAxis.avoidFirstLastClippingEnabled = true
lineChartView.xAxis.labelPosition = .bottom
lineChartView.leftAxis.labelPosition = .insideChart
lineChartView.rightAxis.drawLabelsEnabled = false
lineChartView.legend.enabled = false
lineChartView.data = data
lineChartView.data?.highlightEnabled = true
}
The documentation says:
setLabelCount(int count, boolean force): Sets the number of labels for
the y-axis. Be aware that this number is not fixed (if force == false)
and can only be approximated. If force is enabled (true), then the
exact specified label-count is drawn – this can lead to uneven numbers
on the axis.
see https://weeklycoding.com/mpandroidchart-documentation/axis-general/
Remark: although the y-axis is mentioned in the documentation of the function, it can actually be applied to both axes.
So instead of
lineChartView.xAxis.labelCount = 7
use
lineChartView.xAxis.setLabelCount(9, force: true)
Note: you need 9 label counts not 7.
Test
If one combines a sine wave of 288 points with your code above
for i in 0...288 {
let val = sin(Double(i) * 2.0 * Double.pi / 288.0) + 1
dataEntries.append(ChartDataEntry(x: Double(i), y: val))
}
and forces the label count = 9 as shown, then it looks like this:
Stephan was correct in his answer to this question. To force gridlines and alignment, use setLabelCount(x, force: true) the additional complexity related to my specific project and the data contained in the labels.

How to use SceneKit vortex field to create a tornato effect

In the SceneKit WWDC 2014, they have an example of a vortex field with this effect:
The particle system looks much like a tornato, as it spins inward with a hollow center.
However, the documentation for vortex fields have no information on how to achieve this effect. Right now, I have this:
// create the particle system
let exp = SCNParticleSystem()
exp.loops = true
exp.particleMass = 5
exp.birthRate = 10000
exp.emissionDuration = 10
exp.emitterShape = SCNTorus(ringRadius: 5, pipeRadius: 1)
exp.particleLifeSpan = 15
exp.particleVelocity = 2
exp.particleColor = UIColor.white
exp.isAffectedByPhysicsFields = true
scene.addParticleSystem(exp, transform: SCNMatrix4MakeRotation(0, 0, 0, 0))
// create the field
let field = SCNPhysicsField.vortex()
field.strength = -5
field.direction = SCNVector3(x: 0, y: 1, z: 0)
let fieldNode = SCNNode()
fieldNode.physicsField = field
scene.rootNode.addChildNode(fieldNode)
This creates this effect:
Where I am looking down at the particles rotating clockwise with a really big radius outwards. It looks nothing like a tornato effect. How can I create this effect?
You say tornato, I say tornado, let’s call the whole thing off...
The SceneKit WWDC 2014 demo/slides is a sample code project, so you can see for yourself how they made any of the effects you see therein. In this case, it looks like the “vortex” demo isn’t actually using the vortexField API, but instead the custom field API that lets you supply your own math in an evaluator block. (See the link for the code in that block.)
You might be able to get similar behavior without a custom field by combining a vortex (causes rotation only) with radial gravity (attracts inward) with linear gravity (attracts downward), or some other combination (possibly something involving electric charge). But you’d probably have to experiment with tweaking the parameters quite a bit.
If anyone is still interested in this topic - here is a Swift 5 implementation of that legendary tornado effect.
Here is an example function that will create your tornado.
func addTornadoPhysicsField() {
// Tornado Particles Field Example
guard let tornadoSystem = SCNParticleSystem(named: "tornado.scnp", inDirectory: nil) else { return }
let emitterGeometry = SCNTorus(ringRadius: 1.0, pipeRadius: 0.2)
emitterGeometry.firstMaterial?.transparency = 0.0
let fieldAndParticleNode = SCNNode(geometry: emitterGeometry)
fieldAndParticleNode.position = SCNVector3(0.0, 0.0, -20.0)
tornadoSystem.emitterShape = emitterGeometry
fieldAndParticleNode.addParticleSystem(tornadoSystem)
yourScene.rootNode.addChildNode(fieldAndParticleNode)
// Tornado
let worldOrigin = SCNVector3Make(fieldAndParticleNode.worldTransform.m41,
fieldAndParticleNode.worldTransform.m42,
fieldAndParticleNode.worldTransform.m43)
let worldAxis = simd_float3(0.0, 1.0, 0.0) // i.Ex. the Y axis
// Custom Field (Tornado)
let customVortexField = SCNPhysicsField.customField(evaluationBlock: { position, velocity, mass, charge, time in
let l = simd_float3(worldOrigin.x - position.x, 1.0, worldOrigin.z - position.z)
let t = simd_cross(worldAxis, l)
let d2: Float = l.x * l.x + l.z * l.z
let vs: Float = 27 / sqrt(d2) // diameter, the bigger the value the wider it becomes (Apple Default = 20)
let fy: Float = 1.0 - Float((min(1.0, (position.y / 240.0)))) // rotations, a higher value means more turn arounds (more screwed, Apple Default = 15.0))
return SCNVector3Make(t.x * vs + l.x * 10 * fy, 0, t.z * vs + l.z * 10 * fy)
})
customVortexField.halfExtent = SCNVector3Make(100, 100, 100)
fieldAndParticleNode.physicsField = customVortexField // Attach the Field
}
Additional Configuration Options:
Finally all this can result in something like that:
Note: if you would like to move your static tornado almost like a real tornado, you will have to find a way to re-apply the physics field for each rendererd frame. If you don't, the world origin used in the evaluation block will not move and it will distort your tornado.
Note: You can also split the particle/field node into two different nodes that moves independently from each other. Constrain the field node to the position of the particle node and play around with the influence factor (still need to re-apply the field each frame)
For more information on Custom Fields check out here.

Crash on launch, error with SKphysics Joint

I am creating a game and i am trying to create a character that has 2 different nodes, the legs and the torso. I tried to link them using a fixed type joint, however when i do that the torso slides off of the legs sometimes while moving around. Now i am trying to create the character using a distance limit between the 2 nodes hoping that they won't slide away from each other now,however it keeps crashing on launch any ideas?
here is my code
func CreateHero (){
soldierLegs.position = CGPoint(x: 405 , y: 139)
soldierLegs.zPosition = 1
soldierLegs.anchorPoint.x = 0.6
soldierLegs.anchorPoint.y = 0.7
let soldierLegsBody:SKPhysicsBody = SKPhysicsBody(rectangleOfSize:
soldierLegs.size)
soldierLegsBody.dynamic = true
soldierLegsBody.affectedByGravity = true
soldierLegsBody.allowsRotation = false
//body.restitution = 0.4
soldierLegsBody.categoryBitMask = BodyType.soldierL.rawValue
soldierLegsBody.contactTestBitMask = BodyType.enemy1.rawValue |
BodyType.enemy2.rawValue | BodyType.enemy3.rawValue |
BodyType.desertForegroundCase.rawValue
soldierLegs.physicsBody = soldierLegsBody
soldierTorso.position = soldierLegs.position
soldierTorso.zPosition = 2
soldierTorso.anchorPoint.x = 0.25
soldierTorso.anchorPoint.y = 0.1
let soldierTorsoBody:SKPhysicsBody = SKPhysicsBody(rectangleOfSize:
soldierTorso.size)
soldierTorsoBody.dynamic = true
soldierTorsoBody.affectedByGravity = true
soldierTorsoBody.allowsRotation = false
soldierTorsoBody.categoryBitMask = BodyType.soldierT.rawValue
soldierTorsoBody.contactTestBitMask = BodyType.enemy1.rawValue |
BodyType.enemy2.rawValue | BodyType.enemy3.rawValue |
BodyType.desertForegroundCase.rawValue
soldierTorso.physicsBody = soldierTorsoBody
let Joint =
SKPhysicsJointLimit.jointWithBodyA(soldierLegs.physicsBody!, bodyB:
soldierTorso.physicsBody!, anchorA: soldierLegs.position, anchorB:
soldierTorso.position)
Joint.maxLength = 0.01
self.addChild(soldierTorso)
self.addChild(soldierLegs)
self.physicsWorld.addJoint(Joint)
}
According to the Apple official document :
Creating a Limit Joint
class func joint(withBodyA: SKPhysicsBody, bodyB: SKPhysicsBody, anchorA: CGPoint, anchorB: CGPoint)
Creates a new limit joint.
anchorA
A connection point on the first body in the scene’s coordinate system.
anchorB
A connection point on the second body in the scene’s coordinate system.
In your case anchorA is equal to anchorB (the same point), there is no limit, this one cause the crash.

EXC_BAD_INSTRUCTION when spawning an object

So I'm trying to spawn a falling object in my game and i'm using an array to set all the possible xSpawn points and then I randomise through the array to get an x value but the problem is when It gets to this line in the code:
let SpawnPoint = UInt32(randomX)
It gives me the EXC_BAD_INSTRUCTION error and I can't seem to see why. I'm still new to swift so an explanation as to why it gives me an error at this point would be greatly cherished.
Full Code:
func spawnFallingOjects() {
let xSpawnOptions = [-50, -100, 0, 100, 150]
let randomX = xSpawnOptions[Int(arc4random_uniform(UInt32(xSpawnOptions.count)))]
let Bomb = SKSpriteNode(imageNamed: "YellowFrog")
Bomb.zPosition = 900
let SpawnPoint = UInt32(randomX)
Bomb.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
let action = SKAction.moveToY(-350, duration: 2.0)
Bomb.runAction(SKAction.repeatActionForever(action))
self.addChild(Bomb)
}
Two out of five members of xSpawnOptions are negative numbers, which cannot be represented in an unsigned integer. So when you try to convert them to such with:
let SpawnPoint = UInt32(randomX)
it crashes as you'd expect.
It's not clear what you're trying to do with your random number generation, but one way or another you need to change the logic of it to account for this, perhaps by calculating a random number which is always positive, and then adding or subtracting an offset to it, for example:
let SpawnPoint: UInt32 = 50
Bomb.position = CGPoint(x: CGFloat(Int(arc4random_uniform(SpawnPoint)) + randomX), y: self.size.height)

How to make y-axis not start at zero?

I know there is a chart.leftAxis.startAtZeroEnabled = false option but it's not working for me.
I'm making a line graph to track Instagram Followers over time and I have data points that vary very little so I need the y-axis adjusted so that the changes are enabled, but it
Here is the code I have for the graph:
let chart = LineChartView(frame: chart1View.bounds)
let dayTrackerUnits = trackerCrawler.thisWeeksPoints()
var xVals : [Int] = []
var yVals : [ChartDataEntry] = []
var minimum = 1000000000
var maximum = 0
for i in 0..<dayTrackerUnits.count {
xVals.append(i)
yVals.append(ChartDataEntry(value: Double(dayTrackerUnits[i].followerCount), xIndex: i))
if dayTrackerUnits[i].followerCount < minimum {
minimum = dayTrackerUnits[i].followerCount
}
if dayTrackerUnits[i].followerCount > maximum {
maximum = dayTrackerUnits[i].followerCount
}
}
print(yVals)
let chartDataSet = LineChartDataSet(yVals: yVals, label: "Followers")
chartDataSet.setColor(GlobalStyles.sharedStyles.instagramBlue())
chartDataSet.lineWidth = 2.0
chartDataSet.circleRadius = 5.0
chartDataSet.circleColors = [GlobalStyles.sharedStyles.instagramBlue()]
chartDataSet.drawValuesEnabled = false
chartDataSet.drawVerticalHighlightIndicatorEnabled = true
let data = LineChartData(xVals: xVals, dataSet: chartDataSet)
chart.data = data
chart.delegate = self
chart.leftAxis.startAtZeroEnabled = false
chart.rightAxis.startAtZeroEnabled = false
print("min: \(minimum), max: \(maximum)")
chart.rightAxis.enabled = false
chart1View.addSubview(chart)
left1Label.text = "Followers"
I'd like to set the yAxisMinimum to minimum and the yAxisMaximum to maximum but the graph won't display how I want.
Any suggestions?
You can just use customAxisMin/Max to limit its range.
BTW, you have to make sure your data values does not contains 0. It seems like your data has 0, that's why it always start from 0. It has a small algorithm to calculate the axis range in computeAxisValues in y axis renderer.