Determine if angle is close to zero by +/- 2? - swift

In my app, I'm trying to provide an indication that the user's heading is within a reasonable range from zero. The angle resets to zero if it goes beyond 360. So this is what I'm doing:
let angle1 = Angle(degree: 359)
let angle2 = Angle(degree: 2)
angle1.degrees > 358 || angle1.degrees < 2
angle2.degrees > 358 || angle2.degrees < 2
Is there a built in method or better way to test for this? Also, could there be a scenario where the CoreLocation heading is larger than 360 or a negative number?

What you're doing works, but there is a way to do this in code that I suppose is a more direct/accurate code representation of the logic. That would be to check whether a range of values contains your value. In this case, we're checking whether the ranges 0 to 2 or 358 to 360 contain the relevant current angle in degrees.
As a side note, I like to wrap these sorts of calculations into my models as a computed property. It might look something like...
struct Angle {
var degrees: Double
var isCloseToZero: Bool {
let upperRange = 358.0...360.0
let lowerRange = 0.0...2.0
return lowerRange.contains(degrees) || upperRange.contains(degrees)
}
}
This can be accessed like...
let angle = Angle(degrees: 359)
let isClsoeToZero = angle.isCloseToZero

you could try the following code to achieve what you asked.
var angle = Angle(degrees: 359.0) // Angle(degrees: 2.0)
// between 358 and 360 inclusive or between 0 and +\- 2 inclusive
if angle.degrees >= 358 && angle.degrees <= 360 ||
angle.degrees >= 0 && angle.degrees <= 2 ||
angle.degrees >= -2 && angle.degrees <= 0 {
print("Yes, user is heading within a reasonable range from zero")
}
Regarding CoreLocation course (heading), according to the docs,
https://developer.apple.com/documentation/corelocation/cllocation/1423832-course
"... A negative value indicates that the course information is invalid..."

could there be a scenario where the CoreLocation heading is larger
I'm not sure, but lots of angle-related math might take your value out of the 0..<360 range. To remedy this, you can add make something like:
extension Angle {
// Return a normalized copy of this angle that's guaranteed to be within +0 ..< +360
var normalized: Angle {
let potentiallyNegativeAngle = degrees.truncatingRemainder(dividingBy: 360.0)
let positiveInRangeAngle = (potentiallyNegativeAngle + 360).truncatingRemainder(dividingBy: 360.0)
return Angle(degrees: positiveInRangeAngle) }
}
Is there a built in method or better way to test for this?
No, but it can be pretty fun to write your own. Here's how I would do it:
// It astounds me that these basic operators aren't already built-in
extension Angle {
static func + (minuend: Angle, subtrahend: Angle) -> Angle {
Angle(radians: minuend.radians + subtrahend.radians)
}
static func - (minuend: Angle, subtrahend: Angle) -> Angle {
Angle(radians: minuend.radians - subtrahend.radians)
}
}
extension Angle {
// There's probably some clever way to do this without branching,
// and purely with modular arithmetic, but I couldn't figure it out
func isWithin(_ delta: Angle, of target: Angle) -> Bool {
return self.normalized > (target - delta).normalized ||
self.normalized < (target + delta).normalized
}
func isCloseToZero(delta: Angle = Angle(degrees: 2.0)) -> Bool {
isWithin(delta, of: Angle(degrees: 0))
}
}
Here are some test cases:
print(" -340.0: ", Angle(degrees: -340.0).isCloseToZero()) // False
print(" -358.1: ", Angle(degrees: -358.1).isCloseToZero())
print(" -5.0: ", Angle(degrees: -5.0).isCloseToZero()) // False
print(" -1.9: ", Angle(degrees: -1.9).isCloseToZero())
print(" 0.0: ", Angle(degrees: 0.0).isCloseToZero())
print(" +1.9: ", Angle(degrees: +1.9).isCloseToZero())
print(" +5.0: ", Angle(degrees: +5.0).isCloseToZero()) // False
print(" +358.1: ", Angle(degrees: +358.1).isCloseToZero())
print(" +360.0: ", Angle(degrees: +360.0).isCloseToZero())
print(" +365.0: ", Angle(degrees: +365.0).isCloseToZero()) // False
Here's a fancier variant, which does this totally branch-free using some clever modular arithmetic:
extension Angle {
// Returns the distance between `self` and `target`, in the range `-180..<180` degrees
func distance(to target: Angle) -> Angle {
let rawDistance = (self - target).radians
let normalizedDistance = .pi - abs(abs(rawDistance) - .pi)
return Angle(radians: normalizedDistance)
}
func isWithin(_ delta: Angle, of target: Angle) -> Bool {
let normalizedDelta = delta.normalized
precondition(normalizedDelta.radians <= .pi,
"""
`isWithin(_:of:)` always find the shortest distance between the two angles,
so the delta has to be congruent to an angle between 0 and 180 degrees!
It was \(delta), which normalized to: \(normalizedDelta)
"""
)
return abs(self.distance(to: target).radians) <= normalizedDelta.radians
}
func isCloseToZero(delta: Angle = Angle(degrees: 2.0)) -> Bool {
isWithin(delta, of: Angle(degrees: 0))
}
}

Related

Angle evaluation in swift, strange behavior when angle close to 360

i am having a trouble when evaluating and comparing some angle.
i have create this function which return a Color base on some condition
i'm passing to the function to parameter which are startingHeading and currentHeading
func lh1(startHeading:Double, currentHeading : Double)->Color {
let HDGstart : Angle = Angle(degrees: startHeading)
let HDHCurrent : Angle = Angle(degrees: currentHeading)
var coloreDaMostrare : Color = .black
if HDHCurrent >= HDGstart - Angle(degrees: 10) && HDHCurrent < HDGstart - Angle(degrees: 5) {
coloreDaMostrare = .orange
}
else if HDHCurrent > HDGstart - Angle(degrees: 5){
coloreDaMostrare = .black
}
else if HDHCurrent < HDGstart - Angle(degrees: 10) {
coloreDaMostrare = .orange
}
return coloreDaMostrare
}
it work fine when the startingHeading is above 10 deg, but when I'm close to 0/360 it get crazy because the math HDHStart - 10 give a negative angle.
for swift 2 deg - 3 result in -1 but with angle should be 359...
what I'm doing wrong?
is there any way can fix this issue, I have seen few post online the suggest to use modulo-operator .. I have try but swift game warning say can not use in swift ( and to be honest I don't understand how it work)
looking for some help..
thanks a lot
The following function should allow for input angles both smaller and larger than 0...360 to be converted into that range:
func convertToPositiveAngle(_ angle : Angle) -> Angle {
var toRet = Angle(degrees: fmod(angle.degrees, 360))
if toRet.degrees < 0 {
toRet.degrees += 360.0;
}
return toRet
}
The fmod function is the floating point equivalent of the modulus operator -- it tells you the remainder, basically. So, fmod(370.5, 360) should give you 10.5.
In your example code, every time you do the subtraction, you should use the above equation. So:
if HDHCurrent >= convertToPositiveAngle(HDGstart - Angle(degrees: 10)) && HDHCurrent < convertToPositiveAngle(HDGstart - Angle(degrees: 5)) {
etc

Odd division result in swift [duplicate]

I'm trying to make a math app with different equations and formulas but I'm trying to circle sector but i just wanted to try to divide the input value by 360 but when I do that it only says 0 unless the value is over 360. I have tried using String, Double and Float with no luck I don't know what I'm doing is wrong but down here is the code. I'm thankful for help but I have been sitting a while and searched online for an answer with no result I might have been searching with the wrong search.
if graderna.text == ""{
}
else{
var myInt: Int? = Int(graderna.text!) // conversion of string to Int
var myInt2: Int? = Int(radien.text!)
let pi = 3.1415926
let lutning = 360
let result = (Double(myInt! / lutning) * Double(pi))
svar2.text = "\(result)"
}
Your code is performing integer division, taking the integer result and converting it to a double. Instead, you want to convert these individual integers to doubles and then do the division. So, instead of
let result = (Double(myInt! / lutning) * Double(pi))
You should
let result = Double(myInt!) / Double(lutning) * Double(pi)
Note, Double already has a .pi constant, so you can remove your pi constant, and simplify the above to:
let result = Double(myInt!) / Double(lutning) * .pi
Personally, I’d define myInt and lutning to be Double from the get go (and, while we’re at it, remove all of the forced unwrapping (with the !) of the optionals):
guard
let text = graderna.text,
let text2 = radien.text,
let value = Double(text),
let value2 = Double(text2)
else {
return
}
let lutning: Double = 360
let result = value / lutning * .pi
Or, you can use flatMap to safely unwrap those optional strings:
guard
let value = graderna.text.flatMap({ Double($0) }),
let value2 = radien.text.flatMap({ Double($0) })
else {
return
}
let lutning: Double = 360
let result = value / lutning * .pi
(By the way, if you’re converting between radians and degrees, it should be 2π/360, not π/360.)
You are dividing an Int by an Int.
Integer division rounds to the nearest integer towards zero. Therefore for example 359 / 360 is not a number close to 1, it is 0. 360 / 360 up to 719 / 360 equals 1. 720 / 360 to 1079 / 360 equals 2, and so on.
But your use of optionals is atrocious. I'd write
let myInt = Int(graderna.text!)
let myInt2 = Int(radien.text!)
if let realInt = myInt, realInt2 = myInt2 {
let pi = 3.1415926
let lutning = 360.0
let result = Double (realInt) * (pi / lutning)
svar2.text = "\(result)"
}
In the line let result = (Double(myInt! / lutning) * Double(pi)) you cast your type to double after dividing two integers so your result will always be zero. You have to make them doubles before division.
let result = (Double(myInt!) / Double(lutning)) * Double(pi))
If you want the value should be correct, then try as
let division = ((Float(V1) / Float(V2)) * Float(pi))

Rounding Numbers to Two Significant Figures

I am sure this is an easy question to any of you are experienced in Swift, however, I just started learning how to program and have no idea where to start. What I am trying to do is a round a number to the nearest whole value, or to the third number. This is what I mean:
12.6 //Want rounded to 13
126 //Want rounded to 130
1264 //Want rounded to 1300
I know swift has a .rounded() function, and I have managed to use it to round the nearest 10th, 100th, etc., however, I cannot round the way I would like to. Any advice would be much appreciated.
Here's one way to round any Double or Int (including negative numbers) to a given number of significant figures:
func round(_ num: Double, to places: Int) -> Double {
let p = log10(abs(num))
let f = pow(10, p.rounded() - Double(places) + 1)
let rnum = (num / f).rounded() * f
return rnum
}
func round(_ num: Int, to places: Int) -> Int {
let p = log10(abs(Double(num)))
let f = pow(10, p.rounded() - Double(places) + 1)
let rnum = (Double(num) / f).rounded() * f
return Int(rnum)
}
print(round(0.265, to: 2))
print(round(1.26, to: 2))
print(round(12.6, to: 2))
print(round(126, to: 2))
print(round(1264, to: 2))
Output:
0.27
1.3
13.0
130
1300
As stated by Sulthan you can use NumberFormatter:
let formatter = NumberFormatter()
formatter.usesSignificantDigits = true
formatter.maximumSignificantDigits = 2
formatter.minimumSignificantDigits = 2
if let result = formatter.string(from: 12.6) {
print(result) // prints 13
}
One possibility to implement a rounding algorithm. I suppose you always want the result to be integer.
func round(_ number: Float, to digits: Int) -> Float {
guard number >= 0 else {
return -round(-number, to: digits)
}
let max = pow(10, Float(digits))
var numZeros = 0
var value = number
while (value >= max) {
value /= 10
numZeros += 1
}
return round(value) * pow(10, Float(numZeros))
}
print(round(12.6, to: 2)) // 13
print(round(126, to: 2)) // 130
print(round(1264, to: 2)) // 1300

Increase random number probability swift

I'm trying to make a game where the hero damage is generated randomly within a range but the more luck the hero has, the higher probability the hero will have to hit with the max damage number within that range.
I'm using a Double extension to make it easier on myself.
public extension Double {
public static func random(lower: Double = 0, _ upper: Double = 100) -> Double {
return (Double(arc4random()) / 0xFFFFFFFF) * (upper - lower) + lower
}
//Assigning the random number to a constant
let heroDamage = Double.random(5, 15)
Let's say the hero has now an 80% of probability on getting the max damage(in this case 15), how would I approach this?, Thanks in advance.
For a given max damage probability of pMaxDamage (say, pMaxDamage = 0.80), one simple solution is to generate a random number, say r, in [0,1] and output:
maxDamage if r <= pMaxDamage,
a random damage in range [minDamage, maxDamage], otherwise.
E.g.:
class Hero {
let heroName: String
let pMaxDamage: Double // probability to generate max damage
init(heroName: String, pMaxDamage: Double) {
self.heroName = heroName
self.pMaxDamage = pMaxDamage
}
func generateDamage(minDamage minDamage: Double, maxDamage: Double) -> Double {
let r = (Double(arc4random()) / 0xFFFFFFFF)
return r <= pMaxDamage ? maxDamage
: round(100*((r-pMaxDamage)/(1-pMaxDamage)*(minDamage-maxDamage)+maxDamage))/100
}
}
For the above implementation the r-damage (r uniform random number in [0, 1]) response curve looks as follows:
Example usage:
let myHero = Hero(heroName: "Foo", pMaxDamage: 0.80)
for _ in (1...10) {
print("Slash! <\(myHero.generateDamage(minDamage: 5, maxDamage: 15)) damage>")
}
Example output:
Slash! <15.0 damage>
Slash! <12.68 damage>
Slash! <15.0 damage>
Slash! <15.0 damage>
Slash! <5.72 damage>
Slash! <15.0 damage>
Slash! <15.0 damage>
Slash! <15.0 damage>
Slash! <15.0 damage>
Slash! <15.0 damage>
If you want your damage values to only take integer values, an alternative solution would be roulette wheel selection with
pMaxDamage probability of picking maxDamage,
uniform (1-pMaxDamage)/(numDamagePoints-1) probability of picking any of the remaining {minDamage, minDamage+1, ..., maxDamage-1} damage values.
Maybe add a luck to the random function? Pure linear implementation here. You could use some advanced curves though.
private let MaxLuck = 1000.0
public extension Double {
public static func random(lower: Double = 0, _ upper: Double = 100, luck: Double) -> Double {
let weight = min(luck / MaxLuck, 1)
return (Double(arc4random()) / 0xFFFFFFFF) * weight * (upper - lower) + lower
}
}

TouchesMoved is lagged in swift

I am writing the code in Xcode 6 Beta 6 using Swift in SpriteKit. In the code I need a picture to follow the finger when the user moves it. touchesMoved works but with glitches. If I move the finger slowly everything is fine. If I the move the finger fast going right to left then everything is fine. If I move the finger fast going left to right, then the picture follows the finger only for a fraction of a second. If I tap and hold the picture in its current position for about half a second then everything is fine when I move it fast going both from right to left or left to right. In summary I cannot move the picture fast going left to right unless I tap and hold the picture for about half a second. Anybody has a clue why this is happening? Thanks for your time. Below is the code. I am moving SKSPriteNode follow2
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let angle_area_location = touch.locationInNode(self)
if self.nodeAtPoint(angle_area_location) == self.angle_area {
if (angle_area_location.x <= 21) {
angle = 1.55681122463001
distance12 = sqrt((angle_area_location.y - 30) * (angle_area_location.y - 30) + 1)
}
if (angle_area_location.y <= 31) {
angle = 0.0102037274939542
distance12 = sqrt((31 - 30) * (31 - 30) + (angle_area_location.x - 20) * (angle_area_location.x - 20))
}
if (angle_area_location.x > 21) && (angle_area_location.y > 31) {
angle = atan((angle_area_location.y - 30) / (angle_area_location.x - 20))
distance12 = sqrt((angle_area_location.y - 30) * (angle_area_location.y - 30) + (angle_area_location.x - 20) * (angle_area_location.x - 20))
}
if (distance12 <= maxFollow2) && (distance12 >= minFollow2) {
self.cannon.zRotation = angle
self.arc.zRotation = angle
if (angle_area_location.x > 21) || (angle_area_location.y > 31) {
follow2.position = CGPointMake(angle_area_location.x , angle_area_location.y)
}
if(angle_area_location.x <= 21) {
follow2.position = CGPointMake(21 , angle_area_location.y)
}
if (angle_area_location.y <= 31) {
follow2.position = CGPointMake(angle_area_location.x , 31)
}
}
if(distance12 > maxFollow2) {
self.cannon.zRotation = angle
self.arc.zRotation = angle
delta = 290/3
arc.size = CGSizeMake(160 * (1 + delta/20) , 35)
arc.position = CGPointMake(20 - 3 * (delta) * cos(angle) , 30 - 3 * (delta) * sin(angle))
followdist = 360
follow2.position = CGPointMake(angle_area_location.x , angle_area_location.y)
velocity = vmin + (followdist - minFollow2) * (300/(maxFollow2 - minFollow2))
}
if (distance12 < minFollow2) {
self.cannon.zRotation = angle
self.arc.zRotation = angle
arc.size = CGSizeMake(160 , 6.8)
arc.position = CGPointMake(20 , 30)
follow2.position = CGPointMake( minFollow2 * cos(angle) + 20 , minFollow2 * sin(angle) + 30)
followdist = sqrt((follow2.position.y - 30) * (follow2.position.y - 30) + (follow2.position.x - 20) * (follow2.position.x - 20))
velocity = vmin + (followdist - minFollow2) * (300/(maxFollow2 - minFollow2))
}
}
}
}
Ok I figured the glitch. I had a UISwipeGestureRecognizer that calls a method when I right swipe. I deactivated that and everything works fine. I guess swiping right and moving left to right on touchesMoved are interfering with each other.
A common trick when working with distances is to avoid taking the square root and only compare the squared values. This saves quite a bit of processor resources.
Example:
let maxfollow2sqr = maxFollow2 * maxFollow2
distance12 = (angle_area_location.y - 30) * (angle_area_location.y - 30) + (angle_area_location.x - 20) * (angle_area_location.x - 20)
if (distance12 <= maxFollow2sqr) {
// do something here
}
Since all you care about is if the calculated distance is between the min and the max you can deal just with the squares. This may speed up the function quite a bit but there are probably other optimizations that can be done.