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

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

Related

In scene kit, SCNPhysicsField.noiseField seems to just terminate, end, after a few seconds

Have an SCNNode, add a physics body,
physicsBody = SCNPhysicsBody(type: .dynamic, shape: ..)
physicsBody?.isAffectedByGravity = false
Now add a physics field, for example
physicsField = SCNPhysicsField.noiseField(smoothness: 0.2, animationSpeed: 0.01)
physicsField?.strength = 0.05
It works perfectly. In this case, .noise , the object will jiggle around.
However after a few seconds (often 7 seconds, sometimes a different length of time), the object will simply stop moving.
(The three values, smoothness speed and strength, make no difference if you change them - it will still end after a few seconds.)
What's the solution to this mystery?
Just to be clear, I never used a SCNPhysicsField.noiseField, but I used one of type SCNPhysicsField.linearGravity and another of type SCNPhysicsField.customField and both of them are working correctly and do not stop unexpected as you describe.
here are my examples:
let attractionField = SCNPhysicsField.linearGravity()
attractionField.halfExtent = SCNVector3(250.0, 35.0, 60.0)
attractionField.direction = SCNVector3(-1.0, 0.0, 0.0)
attractionField.strength = 0.2 // 0.15
attractionNode.physicsField = attractionField
and the other one, (which I used to create a tornado):
private func addCustomVortexField() {
// Tornado Particles Field
let worldOrigin = stormNode.presentation.worldPosition
let worldAxis = simd_float3(0.0, 1.0, 0.0)
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
let fy: Float = 1.0 - Float((min(1.0, (position.y / 240.0)))) // rotations, a higher value means more turn arounds (more screwed)
return SCNVector3Make(t.x * vs + l.x * 10 * fy, 0, t.z * vs + l.z * 10 * fy)
})
customVortexField.halfExtent = SCNVector3Make(100, 100, 100)
stormNode.physicsField = customVortexField
stormNode.physicsField?.categoryBitMask = BitMasks.BitmaskTornadoField
}
I hope this is gonna help you in some way. You can also provide me your project, and I will have a look at it.

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

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))
}
}

Relative Strength Index in Swift

I am trying to code an RSI (which has been a good way for me to learn API data fetching and algorithms already).
The API I am fetching data from comes from a reputable exchange so I know the values my algorithm is analyzing are correct, that's a good start.
The issue I'm having is that the result of my calculations are completely off from what I can read on that particular exchange and which also provides an RSI indicator (I assume they analyze their own data, so the same data as I have).
I used the exact same API to translate the Ichimoku indicator into code and this time everything is correct! I believe my RSI calculations might be wrong somehow but I've checked and re-checked many times.
I also have a "literal" version of the code where every step is calculated like an excel sheet. It's pretty stupid in code but it validates the logic of the calculation and the results are the same as the following code.
Here is my code to calculate the RSI :
let period = 14
// Upward Movements and Downward Movements
var upwardMovements : [Double] = []
var downwardMovements : [Double] = []
for idx in 0..<15 {
let diff = items[idx + 1].close - items[idx].close
upwardMovements.append(max(diff, 0))
downwardMovements.append(max(-diff, 0))
}
// Average Upward Movements and Average Downward Movements
let averageUpwardMovement1 = upwardMovements[0..<period].reduce(0, +) / Double(period)
let averageDownwardMovement1 = downwardMovements[0..<period].reduce(0, +) / Double(period)
let averageUpwardMovement2 = (averageUpwardMovement1 * Double(period - 1) + upwardMovements[period]) / Double(period)
let averageDownwardMovement2 = (averageDownwardMovement1 * Double(period - 1) + downwardMovements[period]) / Double(period)
// Relative Strength
let relativeStrength1 = averageUpwardMovement1 / averageDownwardMovement1
let relativeStrength2 = averageUpwardMovement2 / averageDownwardMovement2
// Relative Strength Index
let rSI1 = 100 - (100 / (relativeStrength1 + 1))
let rSI2 = 100 - (100 / (relativeStrength2 + 1))
// Relative Strength Index Average
let relativeStrengthAverage = (rSI1 + rSI2) / 2
BitcoinRelativeStrengthIndex.bitcoinRSI = relativeStrengthAverage
Readings at 3:23pm this afternoon give 73.93 for my algorithm and 18.74 on the exchange. As the markets are crashing right now and I have access to different RSIs on different exchanges, they all display an RSI below 20 so my calculations are off.
Do you guys have any idea?
I am answering this 2 years later, but hopefully it helps someone.
RSI gets more precise the more data points you feed into it. For a default RSI period of 14, you should have at least 200 previous data points. The more, the better!
Let's suppose you have an array of close candle prices for a given market. The following function will return RSI values for each candle. You should always ignore the first data points, since they are not precise enough or the number of candles is not the 14 (or whatever your periods number is).
func computeRSI(on prices: [Double], periods: Int = 14, minimumPoints: Int = 200) -> [Double] {
precondition(periods > 1 && minimumPoints > periods && prices.count >= minimumPoints)
return Array(unsafeUninitializedCapacity: prices.count) { (buffer, count) in
buffer.initialize(repeating: 50)
var (previousPrice, gain, loss) = (prices[0], 0.0, 0.0)
for p in stride(from: 1, through: periods, by: 1) {
let price = prices[p]
let value = price - previousPrice
if value > 0 {
gain += value
} else {
loss -= value
}
previousPrice = price
}
let (numPeriods, numPeriodsMinusOne) = (Double(periods), Double(periods &- 1))
var avg = (gain: gain / numPeriods, loss: loss /numPeriods)
buffer[periods] = (avg.loss > .zero) ? 100 - 100 / (1 + avg.gain/avg.loss) : 100
for p in stride(from: periods &+ 1, to: prices.count, by: 1) {
let price = prices[p]
avg.gain *= numPeriodsMinusOne
avg.loss *= numPeriodsMinusOne
let value = price - previousPrice
if value > 0 {
avg.gain += value
} else {
avg.loss -= value
}
avg.gain /= numPeriods
avg.loss /= numPeriods
if avgLoss > .zero {
buffer[p] = 100 - 100 / (1 + avg.gain/avg.loss)
} else {
buffer[p] = 100
}
previousPrice = price
}
count = prices.count
}
}
Please note that the code is very imperative to reduce the amount of operations/loops and get the maximum compiler optimizations. You might be able to squeeze more performance using the Accelerate framework, though. We are also handling the edge case where you might get all gains or losses in a periods range.
If you want to have a running RSI calculation. Just store the last RSI value and perform the RSI equation for the new price.

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))

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.