Advertisement

Point around point rotation

Started by August 15, 2016 07:42 AM
7 comments, last by Nanoha 8 years, 6 months ago

Hey guys my goal is it to calculate the new coordinates of a point after rotating it around another point.

According to a thread this can be done using the following formula:

ai025.png

I verified that using pen and paper and it turned out well. Now I was going to implement the according function using the GLM library.


glm::vec2 rotate_point(glm::vec2 point_a, glm::vec2 point_b, GLfloat angle)
{
	GLfloat angle_radians = glm::radians(angle);
	
	GLfloat x = point_a.x + (point_b.x - point_a.x) * glm::cos(angle_radians) - (point_b.y - point_a.y) * glm::sin(angle_radians);
	GLfloat y = point_a.y + (point_b.x - point_a.x) * glm::sin(angle_radians) + (point_b.y - point_a.y) * glm::cos(angle_radians);

	return glm::vec2(x, y);
}

According to the documentation both glm::sin and glm::cos need the angle parameter to be in radians. For that reason I am using glm::radians to convert the angle to radians.

So here is where the problem occurs. The values which are returned by my function are not correct and I guess, it is because the cos and sin functions return wrong values (at least in my case).

How could I solve this issue? Are there any other functions in the glm-library which I didn't find or is there another, maybe mathematical approach to solve this problem?

I don't see a problem in your code. Perhaps you can provide a few more lines of code to turn this into a complete program that we can run ourselves to see the problem. If you could do so without depending on external libraries, it would be much better.

Here's one idea to make the code a bit simpler: If you have a function that rotates around the origin, you can write

glm::vec2 rotate_around_center(glm::vec2 point, glm::vec2 center, GLfloat angle) {
  return rotate_around_origin(point - center, angle) + center;
}
Another suggestion is to remove the conversion to radians and use radians consistently throughout your code. There is no reason to keep around degrees anywhere. If you need to display an angle in degrees, make the conversion just before displaying it, but don't pollute the rest of the program with degrees.

A slightly different mathematical approach is using complex numbers to represent both points on the plane and rotations. The point (x,y) becomes the number x+i*y, and the rotation of an angle alpha becomes cos(alpha)+i*sin(alpha). Now applying a rotation around the origin is achieved by simply multiplying the complex numbers corresponding to the point and the rotation.


EDIT: Here's some sample code:
#include <iostream>
#include <complex>

typedef std::complex<float> Complex;

float const Tau = std::atan(1.0f) * 8.0f;
float const Degree = Tau / 360.0f;

Complex rotate_around_origin(Complex point, Complex rotation) {
  return point * rotation;
}

Complex rotate_around_center(Complex point, Complex center, Complex rotation) {
  return rotate_around_origin(point - center, rotation) + center;
}

Complex rotation_from_angle(float angle) {
  return Complex(std::cos(angle), std::sin(angle));
}

int main() {
  Complex p(2.0, 5.0);
  Complex center(1.0, 0.0);
  Complex rotation = rotation_from_angle(90.0f * Degree);
  Complex rotated_p = rotate_around_center(p, center, rotation);

  std::cout << rotated_p << '\n';
}

Advertisement

I don't see a problem in your code. Perhaps you can provide a few more lines of code to turn this into a complete program that we can run ourselves to see the problem. If you could do so without depending on external libraries, it would be much better.

Well my problem is the following:

If I try rotating the point (1,0) around the origin (0,0) I get unprecise output values. I call:


glm::vec2 point = rotate_point(glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 0.0f), 90);

It returns:

x = -4.37113883e-008

y = 1.00000000

I mean this is ok, but I wish x would be exactly 0 here. I traced back this inaccuracy down to the glm::cos function where I calculate:


GLfloat angle_radians = glm::radians(angle);

GLfloat costest = glm::cos(angle_radians);

Here costest = -4.37113883e-008 but should be exactly 0. I mean if I do this calculation using degree in the windows calculator it returns exactly 0. What is the right way to achieve this accuracy in my code.

Let's be honest this small error doesn't break my code neither does it distort the drawing result, still this can't be the best solution.

I agree with Alvaro, you should lose degrees as soon as possible. They are fine for setting values since they tend to be easier to understand (since we get them drilled into our heads at school).


angle = DegToRad(90); // it's radians almost right away.

If your code gets run a lot then it's also a good idea to only work out your sin/cos values once (instead of twice), especially as these are probably the most expensive parts in that function.


    float sinA = glm::sin(angle_radians);
    float cosA = glm::cos(angle_radians);
    GLfloat x = point_a.x + (point_b.x - point_a.x) * cosA - (point_b.y - point_a.y) * sinA;
    GLfloat y = point_a.y + (point_b.x - point_a.x) * sinA + (point_b.y - point_a.y) * cosA;

I went through this on paper and I get the same forumla as you have too.

x = -4.37113883e-008

y = 1.00000000

That's just floats for you. It is so small it might as well be 0. The windows calculator might use doubles, you could switch to doubles too but I wouldn't recommend it. It might also know a few facts such as sin(90) = 1 etc. You can drop quite a few of these sin/cos calls if you know facts about them. I remember doing Calculus and the lecturure would randomly pick people and ask them stuff like "What's cos(pi/3) etc and we'd have to just know theie exact values. There's a bunch of them here. Can be useful if you need to optimize some specific cases (such as rotating 90 degrees can be common).

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

There are two things that come into play here:

- Floating point calculations are inexact most of the time by their very nature

- Neither sine nor cosine can be evaluated exactly; computers use approximations to sin and cos that are "fast and good enough".

With a limited number of bits (float usually has 32) you will always have limited precision, there is no way to prevent this.

Still, -4.3.e-8 is ~ -0.000000043, which is more than close enough to 0, considering this gets rounded to an integer value at some point in the drawing process.

"What's cos(pi/3) etc and we'd have to just know theie exact values. There's a bunch of them here. Can be useful if you need to optimize some specific cases (such as rotating 90 degrees can be common).

Thank you guys for all the advice. While it propably is a good idea to know all those values by heart I tried the following:


const GLfloat pi = glm::pi<float>();

GLfloat x = point_a.x + (point_b.x - point_a.x) * glm::cos(pi / 2) - (point_b.y - point_a.y) * glm::sin(pi / 2);
GLfloat y = point_a.y + (point_b.x - point_a.x) * glm::sin(pi / 2) + (point_b.y - point_a.y) * glm::cos(pi / 2);

The results were exactly the same (as expected :D ). An I know I shouldn't recalculate whose values every time, I will not (promise).

So bottom line -4.37113883e-008 is ok I have to live with it :D

Advertisement
As others have said, floating-point numbers have limited precision and your results are normal. It would have been nice if you had told us what numbers you were getting as part of your initial report.

If you stop thinking in angles and start thinking of the rotation as the primary object to think about, you can get around this problem. If you use complex numbers, like in my code, you can use i as your rotation of 90 degrees. If you are not comfortable with complex numbers, you can still store the cosine and the sine in an object and call that a rotation. Then (0, 1) is exactly the rotation you want.

It would have been nice if you had told us what numbers you were getting as part of your initial report.

Just as an additional information. I am trying to construct a function which draws a Koch Curve according to the number of desired iterations. That would need an angle of 60° or (Pi / 3).

But when I was debugging the rotate_point-function I was using (0,0) as origin and (1,0) as the "rotating" point and an angle of 90° (or Pi / 2), because it's easier to debug.

It would have been nice if you had told us what numbers you were getting as part of your initial report.

Just as an additional information. I am trying to construct a function which draws a Koch Curve according to the number of desired iterations. That would need an angle of 60° or (Pi / 3).

But when I was debugging the rotate_point-function I was using (0,0) as origin and (1,0) as the "rotating" point and an angle of 90° (or Pi / 2), because it's easier to debug.

The Koch curve is a great example of where you can optimise out the cos(angle) all together and just put in 0.5 (cos(60 degrees) = 0.5). Similarly you can drop the sin(60) and put in root(3)/2 instead. Obviously you will calculate it once and store it.

I have a lot of line replacement type fractals in my app so I use a rotate function that takes the precalculated values (instead of an angle) since they get used a great deal. E.g.


Vec2 Rotate(const Vec2& in, float sinAngle, float cosAngle)

static const float cos60 = 0.5;
static const float sin60 = sqrt(3)/2;
rotated = Rotate(vec, cos60, sin60);
Pays off when you are rotating a few 100,000 times.

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

This topic is closed to new replies.

Advertisement