How to use maple to simplify trig expressions involving arccos and cos? - trigonometry

I'm having trouble convincing maple to simplify a complicated trig expression. It appears the bottleneck is that I don't know how to tell maple that it's OK to simplify expressions like:
arccos(cos(x))
into
x
Instead, if I issue:
simplify(arccos(cos(x)));
I just get
arccos(cos(x));
Is there some set of assumes that I should be using? My actual expression is much more complicated so I'd prefer a generic solution where the expressions inside arccos and cos might each be complicated expressions.
Update:
Here's the more complicated simplify example where this came up (or at least where I thought this was the issue):
# Angles
hac := arccos( (lab^2 + lbc^2 - lca^2)/(2*lab*lbc) ):
hcd := arccos( (lbc^2 + lbd^2 - lcd^2)/(2*lbc*lbd) ):
had := hac+hcd:
# length of AD
lad := sqrt( lab^2 + lbd^2 - 2*lab*lbd*cos(had) ):
sin_hbd := lbd*sin(had)/lad:
sin_hbp := sin_hbd:
hbp := arcsin( sin_hbp ):
hap := hac:
hab := Pi - hbp - hap:
# length of BP
lbp := lab*sin_hbp/sin(hab):
# factor we're looking for
s := lbp/lbc:
simplify(s);
produces:
lab lbd sin(%2)
-----------------------------------------------------------------------------------------------
2 2 1/2 lbd sin(%2)
(lab + lbd - 2 lab lbd cos(%2)) sin(arcsin(------------------------------------) + %1) lbc
2 2 1/2
(lab + lbd - 2 lab lbd cos(%2))
2 2 2
lab + lbc - lca
%1 := arccos(------------------)
2 lab lbc
2 2 2
lbc + lbd - lcd
%2 := %1 + arccos(------------------)
2 lbc lbd
The symbols lab,lbc,lca are lengths of a triangle. Similarly lab,lbd,lcd. So the angles h* should all be between 0 and Pi. I'm not sure a priori how simple the expression for s can be made. But all my attempts at assumptions so far (e.g., adding the triangle inequalities explicitly, adding bounds like acer's partial answer below) have not had an effect.

Maple follows the usual convention for the principal value of arccos.
You can simplify arccos(cos(x)) back to just x under the assumptions that x lies with [0,Pi].
simplify(arccos(cos(x))) assuming x>=0, x<=Pi;
x
plot(arccos(x),x=-1..1,tickmarks=[default,piticks]);

Related

How do I rotate smoothly in Godot?

Below is my code (ignore the input variable it is just for movement):
var input = Vector3()
if Input.is_action_pressed("move_forward") and Input.is_action_pressed("move_left"):
rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(1, -1), delta * 7)
input[2] = 1
input[0] = 1
elif Input.is_action_pressed("move_forward") and Input.is_action_pressed("move_right"):
rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(1, 1), delta * 7)
input[2] = 1
input[0] = -1
elif Input.is_action_pressed("move_backward") and Input.is_action_pressed("move_left"):
rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(-1, -1), delta * 7)
input[2] = -1
input[0] = 1
elif Input.is_action_pressed("move_backward") and Input.is_action_pressed("move_right"):
rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(-1, 1), delta * 7)
input[2] = -1
input[0] = -1
else:
if Input.is_action_pressed("move_forward"):
rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(1, 0), delta * 7)
input[2] = 1
if Input.is_action_pressed("move_backward"):
rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(-1, 0), delta * 7)
input[2] = -1
if Input.is_action_pressed("move_left"):
rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(0, -1), delta * 7)
input[0] = 1
if Input.is_action_pressed("move_right"):
rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(0, 1), delta * 7)
input[0] = -1
For some reason, the player is hardly moving at all. Before I just set rotation_degrees.y to the direction but that didn't make a smooth movement. Any help is appreciated!
Getting Input
We have a better way to get a vector from input, let us start there:
var input := Vector3(
Input.get_action_strength("move_left") - Input.get_action_strength("move_right"),
0,
Input.get_action_strength("move_forward") - Input.get_action_strength("move_backward")
)
Addendum: This is the order you had. However, notice that we usually thing of x growing to the right, and - in Godot - forward is negative z. So this is backwards, I don't know if that is intentional.
What is going on here is that get_action_strength gives us the "strength" of the input, which is a number between 0 and 1. It will be 0 or 1 for a digital input, but it could be a value in between for analog input.
Then to get a component of the vector I take the input that would make it positive and subtract the negative. That gives you a value in the range from -1 to 1. Which also means we don't get a unit vector, depending on what you are doing, you might or might not want to normalize it.
Next you want the angle on the xz plane. Well, if you just used a Vector2, that would be trivial. So, instead of the code above, let us use a Vector2:
var input := Vector2(
Input.get_action_strength("move_left") - Input.get_action_strength("move_right"),
Input.get_action_strength("move_forward") - Input.get_action_strength("move_backward")
)
By the way, in Godot 3.4+ and Godot 4.0+ you will be able to use Input.get_vector, which simplifies this further.
It should not be hard to get a Vector3 from that if you need it for something else: Vector3(input.x, 0, input.y)
And now get the angle:
var input_angle := input.angle()
Addendum: You may need to do an operation on the angle to make it usable in 3D. The reason being that the angle is measured from the x axis, but no rotation is looking down the z. I had overlooked that when I wrote this answer. Although I tested all the code here. Without motion I didn't notice the orientation didn't match the direction of motion.
Smooth Rotation
Now, we want to smoothly change rotation_degrees.y to input_angle. There are multiple ways to do it, here are a few.
Before any of them. You are going to need to declare the target angle as a field, so the rotation does not reset when there is no input.
var _target_angle:float
And we are going to store it, whenever there is input:
var input := Vector2(
Input.get_action_strength("ui_left") - Input.get_action_strength("ui_right"),
Input.get_action_strength("ui_up") - Input.get_action_strength("ui_down")
)
if input.length_squared() > 0:
_target_angle = input.angle()
Note: I'm checking length_squared instead of length to avoid a square root.
lerp_angle
Yes, you should be able to use lerp_angle. Even thought, I don't advocate that.
I'll go ahead and add that 7 magic constant you have:
const _rotation_amount:float = 7.0
Then you would do it something like this (which I admit is convenient and short code):
rotation.y = lerp_angle(rotation.y, _target_angle, delta * _rotation_amount)
Please notice I changed rotation_degrees to rotation, that is because you get the angle in radians. Which would have been a problem using atan2 too. You can use deg2rad and rad2deg to do the conversions if you prefer.
With this approach, you have very little control over the interpolation. You do not specify how fast it rotates, nor how much time the rotation should take.
Please notice that the weight of a linear interpolation is not time. You are not telling Godot "go from this value to this value in this amount of time" (you can do that with a Tween, see below).
Also notice that it will advance a more or less stable proportion of the difference of the values each frame. Thus it moves more at the start, and less at the end. That is, this is not linear at all. It has a deceleration effect.
Angular speed
If we are going to work with angular speed, it follows that we need a field to hold the angular speed:
const _angular_speed:float = TAU
Here TAU (which is PI * 2) represent one rotation per second (four rotations per second would be 4 * TAU, and a quarter rotation per second could be 0.25 * TAU, you can associate TAU with one Turn as mnemonic).
Now, we are going to figure out the angle difference (shortest path), to do that we can do this:
var angle_diff := wrapf(_target_angle - rotation.y, -PI, PI)
The wrapf function will "wrap" the value in the range (-PI to PI in this case), so that if the values goes beyond that range, it is as if it entered from the opposite edge (if you say wrapf(14, 0, 10) you get 4, and if you say wrapf(-4, 0, 10) you get 6). I hope that makes sense.
To apply the angular speed, in theory, we would only need the sign:
rotation.y += delta * _angular_speed * sign(angle_diff)
However, we don't want to overshoot. We can solve this with clamp:
rotation.y += clamp(delta * _angular_speed, 0, abs(angle_diff)) * sign(angle_diff)
Notice I have separated the sign from the magnitude of angle_diff. I'm clamping how much the angle would change according to angular speed to the magnitude of angle_diff (so it is never more than angle_diff, i.e. it does not overshoot). Once clamped, I apply the sign so it turns in the correct direction.
And, of course, if you really wanted to, you could work with angular acceleration and deceleration too.
Tween
Using a Tween node is the most powerful option sans using an AnimationPlayer. And it is also very easy to use.
We, of course, need to define a Tween object and add it as a child:
var _tween:Tween
func _ready() -> void:
_tween = Tween.new()
add_child(_tween)
This is because the Tween object will continue to gradually change the rotation each frame (so we don't have to worry about it). And to do that, it needs to persist and to get frame notifications, so it needs to be in the scene tree somewhere.
In Godot 4.0+, you will be able to do get_tree().create_tween() which returns a rooted Tween, so you don't have to worry about keeping a reference or adding it to the scene tree.
And then you can do this:
var input := Vector2(
Input.get_action_strength("ui_left") - Input.get_action_strength("ui_right"),
Input.get_action_strength("ui_up") - Input.get_action_strength("ui_down")
)
if input.length_squared() > 0:
var rotation_time := 0.2
_tween.interpolate_property(self, "rotation:y", rotation.y, input.angle(), rotation_time)
_tween.start()
Do not forget to call start. Well, In Godot 4.0+, all tween will automatically start by default.
As you can see, the code is telling the Tween to interpolate the y of the rotation from the value of rotation.y (its current value) to the value of input.angle(), and we specify a rotation time (which I made a variable just so I can give it a name). And that's it.
In Godot 4.0+, interpolate_property is gone in favor of the new tween_property API, with which you don't need to specify the start value.
Oh, wait, we can specify how do you want to ease these values:
_tween.interpolate_property(
self,
"rotation:y",
rotation.y,
input.angle(),
rotation_time,
Tween.TRANS_QUAD,
Tween.EASE_OUT
)
See this cheatsheet by wandomPewlin to pick your easing:
I believe Tween.TRANS_QUAD and Tween.EASE_OUT gives you something close to what you get from lerp_angle, except stable. And look, you have more options!
I'll also mention that you can ask Tween what interpolation it currently is doing, and also remove them, which is useful in some cases. Yet, you don't need to go into that for simple cases like this one. And, well, there is AnimationPlayer if you need something more complex. By the way, yes, AnimationPlayer can operate on a subproperty (such as rotation.y, I have an explanation elsewhere).

Is there an alternative way to convert points from the axial to the projected plane in VB6?

(Please note that I have also posted this to the Visual Basic 6 Programming list on Yahoo! Groups but that seems to have been dead since 2017 so I thought I would post here as well.)
I've recently taken over maintaining a VB6 program at work that has a routine to convert data points from an axial plane to the projected plane given the axial point, a lead measurement, projection angle, vector (initial angle, final angle, gradient) and the projected point. It is written as a function that returns a Boolean to indicate whether it was successful.
It has to calculate the rotation angle from the axial plane to do this and uses the Newton-Raphson iteration method to determine the value of this angle. It sets an upper limit of 30 iterations in which to do this, but for a certain set of parameters, it's unable to find a solution within those 30 iterations.
Does anyone know of an alternative method to perform this conversion please? This program is used to drive a CNC machine with the data points calculated and I believe that these parameters are valid it's just the method used that's preventing it from proceeding.
I have added logging output code to trace the problem and found that it is down to a conversion between the axial and projected planes with a specific upper value for the projection angle (it also uses +/- a margin on this value as part of the process).
The iteration code cannot find a solution that falls within the set tolerance within the set maximum number of tries (30).
I have tried increasing the maximum number of tries, reducing the increments, increasing the tolerance and also reducing the value of the margin applied, but none of what I have done so far works, or it duplicates the same error.
I expect the output to be an array of profile points that are on the projected plane and equivalent to the provided axial plane points using the provided lead measurement and projection angle but I am getting errors that indicate the iteration process failed to find a solution within the maximum number of 30 tries.
Edited to add:
I've written the following pseudocode based on the VB6 code to try and explain how the routine works:
'AxialPoint, ProjectedPoint: structures with ProfileRadius, ProfileWidth,
'NormalAngle, UnwrapAngle and Attribute fields
'Lead: lead measurement
'ProjectionAngle: projection angle onto the projected plane in degrees
'Vector(): provides initial value of Beta, Last Beta value found, Last
'gradient) - set by the calling routine as (0, 0, 0) in this case
'Beta is the angle of rotation from the axial plane
Function AxialToProjected(AxialPoint, Lead, ProjectionAngle, Vector(),
ProjectedPoint):
LeadAngle := Atn(Lead / (2 * PI * AxialPoint.ProfileRadius)) 'radians
SA := Sin(LeadAngle)
CA = Cos(LeadAngle)
CT := -CosD(AxialPoint.NormalAngle)
ST := SinD(AxialPoint.NormalAngle)
SG := SinD(ProjectionAngle)
CG := CosD(ProjectionAngle)
C1 := SA * CG * CT
C2 := CA * CG * ST
C3 := CA * SG * CT
Iteration := 0
FBeta := 0.0001
While (Abs(FBeta) >= 0.0001) And (Iteration < 9) Do
NewValue(Beta, OldBeta, FBeta, Increment:=0.1, Gradient, SVB(),
Iteration, Multiplier:=10)
SB := Sin(Beta)
CB := Cos(Beta)
OldFBeta := FBeta
FBeta := C1 * CB + C2 * SB - C3
If Iteration >= 2 Then
Gradient := (Beta - OldBeta) / (FBeta - OldFBeta)
End If
End While
If (Abs(FBeta) > 0.0001) Then
'The code reaches this point with the specific parameters
Report "Iteration failed to find a solution within 30 tries" error
Else
'The code doesn't reach this point
X3 := Beta * Lead / 2 * PI + AxialPoint.ProfileWidth
With ProjectedPoint Do
.ProfileWidth := X3 * CG - AxialPoint.ProfileRadius * SB * SG
.ProfileRadius := AxialPoint.ProfileRadius * CB
.NormalAngle := ATan2(CA * CB * CG * ST - SA * SB * CG * CT,
-CA * CT) * RadToDeg
.Attribute := AxialPoint.Attribute
.UnwrapAngle := AxialPoint.UnwrapAngle
End With
End If
End AxialToProjected()
SinD() and CosD() accept an angle in degrees and convert it to radians before passing that to the normal Sin() and Cos() functions.
The NewValue() routine calculates a new value for Beta from the value of OldBeta using FBeta, Increment, Gradient, SVB() and the Multiplier while incrementing the value of Iteration:
'Beta: current axial plane rotation angle estimate
'OldBeta: previous current axial plane rotation angle estimate
'FBeta: gradient of tangent to the normal
'Increment: increment size to adjust Beta by
'Gradient: gradient for the Newton method
NewValue(Beta, OldBeta, FBeta, Increment, Gradient, SVB(), Iteration,
Multiplier):
If Iteration > 2 Then
MaxChange := Abs(Increment * Multiplier)
Change := -FBeta * Gradient
If Abs(Change) > MaxChange Then
Beta := Beta + TransferSign(MaxChange, Change)
'TransferSign() will return -MaxChange if Change < 0 or
'MaxChange if Change >= 0
Else
Beta := Beta + Change
End If
ElseIf Iteration = 2 Then
If SVB(3) = 0 Then
Beta := SVB(1) + Increment
Else
Beta := -FBeta * SVB(3)
End If
Else
Beta := SVB(1) 'Set Beta to initial value
End If
End NewValue()
I'll get a diagram uploaded later as I need to work out how best to draw it from my scribblings :)
Just to add some extra context, the routine is being used to calculate points to machine ZA and ZN type worms using a CNC machine.
It's just 1 specific set of parameters for each type that cause the error to occur and I believe that it's being caused by the initial estimate being too far away from the root to be resolved within the 30 try limit but I'm not sure how to work out a better initial estimate.
Edited to add:
Apologies for not returning to the thread sooner as I've been looking into this issue from other angles, but I have managed to put together a simple diagram showing how these planes are related so I hope this helps:
Coordinate Planes
When I was looking into the calculation of FBeta in the code, I found something useful in my internet searches based on Pythagoras' Theorem:
|C3| \le \sqrt{C1^2+C2^2} - the Newton iteration loop will be able to find a solution
|C3| > \sqrt{C1^2+C2^2} - the Newton iteration loop will not be able to find a solution
I've modified the code to perform this check at the start and only enter the Newton loop if equation (1) holds true, otherwise, it currently reports an error, but I'd like to find an alternative method to find a solution.

Line segment intersection

I found this code snippet on raywenderlich.com, however the link to the explanation wasn't valid anymore. I "translated" the answer into Swift, I hope you can understand, it's actually quite easy even without knowing the language. Could anyone explain what exactly is going on here? Thanks for any help.
class func linesCross(#line1: Line, line2: Line) -> Bool {
let denominator = (line1.end.y - line1.start.y) * (line2.end.x - line2.start.x) -
(line1.end.x - line1.start.x) * (line2.end.y - line2.start.y)
if denominator == 0 { return false } //lines are parallel
let ua = ((line1.end.x - line1.start.x) * (line2.start.y - line1.start.y) -
(line1.end.y - line1.start.y) * (line2.start.x - line1.start.x)) / denominator
let ub = ((line2.end.x - line2.start.x) * (line2.start.y - line1.start.y) -
(line2.end.y - line2.start.y) * (line2.start.x - line1.start.x)) / denominator
//lines may touch each other - no test for equality here
return ua > 0 && ua < 1 && ub > 0 && ub < 1
}
You can find a detailed segment-intersection algorithm
in the book Computational Geometry in C, Sec. 7.7.
The SegSegInt code described there is available here.
I recommend avoiding slope calculations.
There are several "degenerate" cases that require care: collinear segments
overlapping or not, one segment endpoint in the interior of the other segments,
etc. I wrote the code to return an indication of these special cases.
This is what the code is doing.
Every point P in the segment AB can be described as:
P = A + u(B - A)
for some constant 0 <= u <= 1. In fact, when u=0 you get P=A, and you getP=B when u=1. Intermediate values of u will give you intermediate values of P in the segment. For instance, when u = 0.5 you will get the point in the middle. In general, you can think of the parameter u as the ratio between the lengths of AP and AB.
Now, if you have another segment CD you can describe the points Q on it in the same way, but with a different u, which I will call v:
Q = C + v(D - C)
Again, keep in mind that Q lies between C and D if, and only if, 0 <= v <= 1 (same as above for P).
To find the intersection between the two segments you have to equate P=Q. In other words, you need to find u and v, both between 0 and 1 such that:
A + u(B - A) = C + v(D - C)
So, you have this equation and you have to see if it is solvable within the given constraints on u and v.
Given that A, B, C and D are points with two coordinates x,y each, you can open the equation above into two equations:
ax + u(bx - ax) = cx + v(dx - cx)
ay + u(by - ay) = cy + v(dy - cy)
where ax = A.x, ay = A.y, etc., are the coordinates of the points.
Now we are left with a 2x2 linear system. In matrix form:
|bx-ax cx-dx| |u| = |cx-ax|
|by-ay cy-dy| |v| |cy-ay|
The determinant of the matrix is
det = (bx-ax)(cy-dy) - (by-ay)(cx-dx)
This quantity corresponds to the denominator of the code snippet (please check).
Now, multiplying both sides by the cofactor matrix:
|cy-dy dx-cx|
|ay-by bx-ax|
we get
det*u = (cy-dy)(cx-ax) + (dx-cx)(cy-ay)
det*v = (ay-by)(cx-ax) + (bx-ax)(cy-ay)
which correspond to the variables ua and ub defined in the code (check this too!)
Finally, once you have u and v you can check whether they are both between 0 and 1 and in that case return that there is intersection. Otherwise, there isn't.
For a given line the slope is
m=(y_end-y_start)/(x_end-x_start)
if two slopes are equal, the lines are parallel
m1=m1
(y1_end-y_start)/(x1_end-x1_start)=(y2_end-y2_start)/(x2_end-x2_start)
And this is equivalent to checking that the denominator is not zero,
Regarding the rest of the code, find the explanation on wikipedia under "Given two points on each line"

What can be a newbie explanation for a dynamic programming?

I am trying to learn the basics of Dynamic Programming (DP), and went through some the online resources i could get, such as...
What is dynamic programming?
Good examples, articles, books for understanding dynamic programming
Tutorial for Dynamic Programming
Dynamic Programming – From Novice to Advanced -- (I can't understand it properly (i.e. how to approach a problem using DP))
and till now i get to understand that
A dynamic problem is almost same that of
recursion with just a difference (which gives it the power it is know
for)
i.e. storing the value or solution we got and using it again to find next solution
For Example:
According to an explanation from codechef
Problem : Minimum Steps to One
Problem Statement: On a positive integer, you can perform any one of the following 3 steps.
Subtract 1 from it. ( n = n - 1 )
If its divisible by 2, divide by 2. ( if n % 2 == 0 , then n = n / 2 )
If its divisible by 3, divide by 3. ( if n % 3 == 0 , then n = n / 3 )
Now the question is, given a positive integer n, find the minimum number of steps that takes n to 1
eg:
For n = 1 , output: 0
For n = 4 , output: 2 ( 4 /2 = 2 /2 = 1 )
For n = 7 , output: 3 ( 7 -1 = 6 /3 = 2 /2 = 1 )
int memo[n+1]; // we will initialize the elements to -1 ( -1 means, not solved it yet )
Top-Down Approach for the above problem
int getMinSteps ( int n ) {
if ( n == 1 ) return 0;
if( memo[n] != -1 ) return memo[n];
int r = 1 + getMinSteps( n - 1 );
if( n%2 == 0 ) r = min( r , 1 + getMinSteps( n / 2 ) );
if( n%3 == 0 ) r = min( r , 1 + getMinSteps( n / 3 ) );
memo[n] = r ; // save the result. If you forget this step, then its same as plain recursion.
return r;
}
Am i correct in understanding the dp, or can anyone explain it in a better and easy way, so that i can learn it and can approach a problem with Dynamic programming.
The Fibonacci sequence example from wikipedia gives a good example.
Dynamic programming is an optimization technique that transforms a potentially exponential recursive solution into a polynomial time solution assuming the problem satisfies the principle of optimality. Basically meaning you can build an optimal solution from optimal sub-problems.
Another important characteristic of problems that are tractable with dynamic programming is that they are overlapping. If those problems are broken down into sub-problems that are repetitive, the same solution can be reused for solving those sub problems.
A problem with optimal substructure property and overlapping subproblems, dynamic programming is a potentially efficient way to solve it.
In the example you can see that recursive version of the Fibonacci numbers would grow in a tree like structure, suggesting an exponential explosion.
function fib(n)
if n <=1 return n
return fib(n − 1) + fib(n − 2)
So for fib(5) you get:
fib(5)
fib(4) + fib(3)
(fib(3) + fib(2)) + (fib(2) + fib(1))
And so on in a tree like fashion.
Dynamic programming lets us build the solution incrementally using optimal sub-problems in polynomial time. This is usually done with some form of record keeping such as a table.
Note that there are repeating instances of sub problems, i.e. calculating fib(2) one time is enough.
Also from Wikipedia, a dynamic programming solution
function fib(n)
if n = 0
return 0
else
var previousFib := 0, currentFib := 1
repeat n − 1 times // loop is skipped if n = 1
var newFib := previousFib + currentFib
previousFib := currentFib
currentFib := newFib
return currentFib
Here the solution is built up from previousFib and currentFib which are set initially. The newFib is calculated from the previous steps in this loop. previousFib and currentFib represent our record keeping for previous sub-problems.
The result is a polynomial time solution (O(n) in this case) for a problem whose recursive formulation would have been exponential (O(2^n) in this case).
There is wonderful answer How should I explain dynamic programming to a 4-year-old?
Just quoting same here :
Writes down "1+1+1+1+1+1+1+1 =" on a sheet of paper
"What's that equal to?"
counting "Eight!"
writes down another "1+" on the left
"What about that?"
quickly "Nine!"
"How'd you know it was nine so fast?"
"You just added one more"
"So you didn't need to recount because you remembered there were
eight! Dynamic Programming is just a fancy way to say 'remembering
stuff to save time later'"

Logic for "slight right turn" given three points

Given three co-planar (2D) points (X1, Y1), (X2, Y2), and (X3, Y3), which represent (respectively ...) "1=where I was, 2=where I am, and 3=where I am going," I need a simple algorithm that will tell me, e.g.
Veer to the right
Make a slight left turn
Turn to the left
In other words, (a) is the turn to the left or to the right; and (b) how sharp is the turn (letting me be arbitrary about this).
For the first part, I've already learned about how to use (see wikipedia: Graham Scan, and question #26315401 here) cross-product to determine whether the turn is to the left or to the right, based on whether the path is counterclockwise.
And, I'm sure that ATAN2() will be at the core of determining how sharp the turn is.
But I can't .. quite .. wrap my head around the proper math which will work in all orientations. (Especially when the angle crosses the zero-line. (A bearing of 350 degrees to 10 degrees is a gap of 20 degrees, not 340, etc.)
Okay, I'm tired. [... of bangin' my head against the wall this mornin'.] "Every time I think I've got it, I'm not sure." So, okay, it's time to ask ... :-)
When you calculate direction change angles with Atan2, don't bother about absolute angles. You have not to calculate two bearings and subtract them - Atan2 can give you relative angle between the first and the second vectors in range -Pi..Pi (-180..180) (range might depend on programming language).
x12 = x2-x1
y12 = y2-y1
x23 = x3-x2
y23 = y3-y2
DirChange = Atan2(x12*y23-x23*y12, x12*x23+y12*y23)
Some explanations: we can calculate sine of vector-vector angle through cross product and vector norms (|A| = Sqrt(A.x*A.x + A.y*A.y)):
Sin(A_B) = (A x B) / (|A|*|B|)
and cosine of vector-vector angle through dot (scalar) product and vector norms:
Cos(A_B) = (A * B) / (|A|*|B|)
Imagine that Atan2 calculates angle with sine and cosine of this angle, excluding common denominator (product of norms)
A_B = Atan2(Sin(A_B), Cos(A_B))
Example in Delphi:
var
P1, P2, P3: TPoint;
x12, y12, x23, y23: Integer;
DirChange: Double;
begin
P1 := Point(0, 0);
P2 := Point(1, 0);
P3 := Point(2, 1);
x12 := P2.X - P1.X;
y12 := P2.Y - P1.Y;
x23 := P3.X - P2.X;
y23 := P3.Y - P2.Y;
DirChange := Math.ArcTan2(x12 * y23 - x23 * y12, x12 * x23 + y12* y23);
Memo1.Lines.Add(Format('%f radians %f degrees',
[DirChange, RadToDeg(DirChange)]));
Output:
0.79 radians 45.00 degrees (left turn)
for your example data set (1,1), (3,2), and (6,3)
-0.14 radians -8.13 degrees (right turn)
EDITED - THE FOLLOWING IS WRONG. My ORIGINAL response was as follows ...
I'm not coming up with the expected answers when I try to use your response.
Let's say the points are: (1,1), (3,2), and (6,3). A gentle right turn.
Using a spreadsheet, I come up with: X12=2, X23=3, Y12=1, Y23=3, and the ATAN2 result (in degrees) is 101.3. A very sharp turn of more than 90 degrees. The spreadsheet formula (listing X1,Y1,X2,Y2,X3,Y3,X12,X23,Y12,Y23 and answer) on row 2, is:
=DEGREES(ATAN2(G2*J2-H2*I2; G2*I2+H2*J2))
(The spreadsheet, OpenOffice, lists "X" as the first parameter to ATAN2.)
Are you certain that the typo is on my end?
AND, AS A MATTER OF FACT (SO TO SPEAK), "YES, IT WAS!"
(Heh, I actually had said it myself. It just didn't occur to me to, like, duh, swap them already.)
My spreadsheet's version of the ATAN2 function specifies the X parameter first. Most programming languages (Delphi, Perl, PHP ...) specify Y first, and that's how the (correct!) answer was given.
When I edited the formula, reversing the parameters to meet the spreadsheet's definition, the problem went away, and I was able to reproduce the values from the (edited) reply. Here's the corrected formula, with the parameters reversed:
=DEGREES(ATAN2(G2*I2+H2*J2; G2*J2-H2*I2))
^^== X ==^^ ^^== Y ==^^
Again, this change in formula was needed because this spreadsheet's implementation of ATAN2 is backwards from that of most programming language implementations. The answer that was originally given, which lists Y first, is correct for most programming languages.
Well, I seem to still be having a bit of a problem . . .
If the points are very far away from each other in nearly a straight line, I am coming up with "large angles." Example:
P1: (0.60644,0.30087) ..
P2: (0.46093,0.30378) ..
P3: (0.19335,0.30087)
The X-coordinate increases but the Y-coordinate remains almost the same.
x12=-0.145507 .. y12=-0.00290698
x23=-0.267578125 .. y23=0.002906976
(I inverted the Y differences because the coordinates are in quadrant-IV, where Y increases downward.)
x=-0.000354855 .. y=-0.00120083
ans= -106.462 (degrees)
Since the points are very nearly co-linear, I expected the answer to be very small. As you can see, it's more than 106 degrees.

Resources