简体   繁体   中英

Rotating 2D Vector by unknown angle such that its direction vector is [1,0]

I am trying to rotate a vector [x,y] around the origin such that when the rotation is completed it lies on the X axis. In order to do this, I'm first computing the angle between [x,y] and [1,0] , then applying a simple 2D rotation matrix to it. I'm using numericjs to work with the vectors.

math.angleBetween = function(A, B) {                                                                                               
    var x = numeric.dot(A, B) / (numeric.norm2(A) * numeric.norm2(B));                                                             
    if(Math.abs(x) <= 1) {                                                                                                         
        return Math.acos(x);                                                                                                       
    } else {                                                                                                                       
        throw "Bad input to angleBetween";                                                                                         
    }                                                                                                                              
};

math.alignToX = function(V) {
    var theta = -math.angleBetween([1,0], V);
    var R = [[Math.cos(theta), -Math.sin(theta)],
             [Math.sin(theta), Math.cos(theta)]];
    return numeric.dot(R, V);
};

(Note: math is a namespace object within my project. Math is ye olde math object.)

This code works sometimes , however there are occasions where no matter how many times I run math.alignToX the vector never even gets close to aligning with the X axis. I'm testing this by checking if the y coordinate is less than 1e-10 .

I've also tried using Math.atan2 with an implicit z coordinate of 0, but the results have been the same. Errors are not being thrown. Some example results:

math.alignToX([152.44444444444434, -55.1111111111111]) 
// result: [124.62691466033475, -103.65652585400568]
// expected: [?, 0]

math.alignToX([372, 40])
// result: [374.14435716712336, -2.0605739337042905e-13]
// expected: [?, 0]
// this value has abs(y coordinate) < 1e-10, so its considered aligned

What am I doing wrong?

If you're rotating something other than your vector, then you'll need to use your R matrix. But if you just need to rotate your vector, the result will be [Math.sqrt(x*x+y*y),0] .

Actually, the task of building a rotation matrix that aligns a known 2d vector with [1, 0] doesn't require any trigonometric functions at all.

In fact, if [xy] is your vector and s is its length (s = Sqrt(x*x + y*y)), then the transformation that maps [xy] to align with [1 0] (pure rotation, no scaling) is just:

          [ x y]
T = 1/s^2 [-y x]

For example, suppose your vector is [Sqrt(3)/2, 1/2]. This is a unit vector as you can easily check so s = 1.

    [Sqrt(3)/2     1/2   ]
T = [  -1/2     Sqrt(3)/2]

Multiplying T by our vector we get:

    [Sqrt(3)/2     1/2   ][Sqrt(3)/2]   [1]
T = [  -1/2     Sqrt(3)/2][  1/2    ] = [0]

So in finding the rotation angle (which in this case is Pi/6) and then creating the rotation matrix, you've just come full circle back to what you started with. The rotation angle for [Sqrt(3)/2, 1/2] is Pi/2, and cos(Pi/2) is Sqrt(3)/2 = x, sin(pi/2) is 1/2 = y.

Put another way, if you know the vector, you ALREADY know the sine and cosine of it's angle with the x axis from the definition of sine and cosine:

cos a = x/s
sin a = y/s where s = || [x, y] ||, is the length of the vector.

My problem is so mind-bendingly obvious that I cannot believe I didn't see it. While I'm checking the domain of Math.acos , I'm not checking the range at all! The problem occurs when the vector lies outside of the range (which is [0,PI] ). Here is what I did to fix it:

math.alignToX = function(V) {
    var theta = -math.angleBetween([1,0], V);
    var R = [[Math.cos(theta), -Math.sin(theta)],
             [Math.sin(theta), Math.cos(theta)]];
    var result = numeric.dot(R, V);
    if(Math.abs(result[1]) < ZERO_THRESHOLD) {
        return result;
    } else {
        V = numeric.dot([[-1, 0], [0, -1]], V); // rotate by PI
        theta = -math.angleBetween([1,0], V);
        R = [[Math.cos(theta), -Math.sin(theta)],
             [Math.sin(theta), Math.cos(theta)]];
        result = numeric.dot(R, V);
        if(Math.abs(result[1]) < ZERO_THRESHOLD) {
            return result;
        } else {
            throw "Unable to align " + V; // still don't trust it 100%
        }
    }
};

For the broken example I gave above, this produces:

[162.10041088743887, 2.842170943040401e-14]

The Y coordinate on this result is significantly less than my ZERO_THRESHOLD (1e-10). I almost feel bad that I solved it myself, but I don't think I would have done so nearly as quickly had I not posted here. I saw the problem when I was checking over my post for typos.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM