Vectors and angles

So - I think I’m missing something fundamental. Or maybe not.

I have a player (a space ship) that rotates fixed about the center of the screen. I have a variable, r, that represents its angle of rotation compared to some arbitrary origin. So far, so good.

Now, I want to be able to touch, and turn toward that touch. I’ve done it like this:


t = vec2(CurrentTouch.x-w2, CurrentTouch.y-w2)
r = math.deg(origin:angleBetween(t))-90

Origin is a vector that defines that arbitrary origin I mentioned. The 90 is a fudge factor because my model was sideways.

This works. But now I want it to not “snap” to the touch, but turn at some maximum angular speed - say 5 degrees per frame. This being a game-level approximation of rotational inertia.

Here’s the issue - I’m starting to have to convert back and forth between angles and vectors and I am concerned about cumulative effects on the angles and it’s just becoming ugly and non-elegant. And non-elegant is often a sign I’m doing something wrong.

There’s a part of me says I should be able to do this all with vectors, elegantly. I know rotate() takes degrees, but I have this gut feeling the rest should be all in vectors. There’s a part of my spine screaming “dot product” at me, but I’ve not been a math major for a loooong time.

So - someone who knows the math better (Andrew, I’m looking at you, but anyone might know better than I - this sort of thing for me has always been very trial-and-error.) to say “nope, it’s complex, that’s just what you do” or “how pitiful, you do this and this -see?”.
I would normally just pummel it into submission, but when I make code for others to see… Well, I should try to set a better example. And I am not proud - I freely admit my ignorance, I seek to learn the right way to do it.

In the end, what I really want is “turnToward(x,y)” such that it observes some maximum rotation, and so that it rotates thru the smallest angle. I’ve done this before by brute force (did I ever share my “battle” JavaScript stuff? Remind me to… It’s pretty fun if you don’t view source) but one of the issues with brute force is I look at my own JavaScript code and say “ewww…”

http://home.bortels.us/planets/battle.html?5

Best viewed on a pc - this is painfully slow on an iPad. On a modern browser on the pc, it’s fun.

Oh, and warning: sound. Boom. Just sayin.

Bortels,

I wrote a function to handle differences between angles in the days before vec2 had been completely fixed up to handle angular differences properly. It looks like this:

`<br />
function GetDifferenceAngle(mra1,mra2)
    -- Input angles in degrees
    -- Output in degrees
    local v1 = vec2(math.sin(math.rad(mra1)),math.cos(math.rad(mra1)))
    local v2 = vec2(math.sin(math.rad(mra2)),math.cos(math.rad(mra2)))   
    local angle = math.deg(math.atan2(v2:cross(v1),v2:dot(v1)))
    
    return angle
end
`

MRA here stands for Mean-Response-Angle but it can be any angle in degrees. You could work entirely in radians if you wanted. It takes the arctan of the vector cross product with its dot product. You get an angle as a return value and you never need to worry about fudge factors. Once you have the angle you can incrementally adjust the heading of your moving entity until it is going in the newly desired direction.

Here is a snippet from a class where I am changing the direction of a entity after computing the difference angle:

`<br />
function Entity:Turn(del_time)
    local v1 = vec2(math.sin(self.course),math.cos(self.course))
    local v2 = vec2(math.sin(self.ordered_cse),math.cos(self.ordered_cse))
    
    local angle = math.deg(math.atan2(v2:cross(v1),v2:dot(v1)))
    
    local sign = 1.0
    
    if angle < 0 then
        sign = -1.0
    end
    
    if math.abs(angle) > 0.1 then
        local del_cse = sign*self.turn_rate*del_time
        local cse_deg = math.deg(self.course)
        local new_cse = math.rad((cse_deg + del_cse)%360)
        self:SetCourse(new_cse)
    else
        self:SetCourse(self.ordered_cse)
    end
end
`

I do not know if this helps … This example shows a straight line where an arrow head should be placed on top. The correct angle of the line with respect to start and stop is determined, and the arrowhead is rotated accordingly. Although a simple example, it took me a while to figure that out.

-- Use this function to perform your initial setup
function setup()
    print("Basic function: Arrow\
")
    print("by CrazyEd, 20120106_V01\
")
    print("An arrow is drawn from point (x0,y0) to point (x,y). Three optional parameters " ..
          "can be defined, dealing with color, arrowhead size and line thickness")
    cyan=color(0,255,255,255)
end

-- This function gets called once every frame
function draw()
    -- This sets the background color to black
    background(0, 0, 0)

    iparameter("x",0,WIDTH,200)
    iparameter("y",0,HEIGHT,100)
    iparameter("arrowsize",0,10,6)
    iparameter("thickness",0,10,4)
    arrow(WIDTH/2,HEIGHT/2,x,y,cyan,arrowsize,thickness)
    arrow(200,200,600,400)
end

function arrow(x0, y0, x, y, col, arrowsize, thickness)
    -- draws a line with an arrow, starting from point(x0,y0) to (x,y) ending with an arrow.
    -- color can be defined with optional parameter col
    -- arrow head size can be scaled with optional parameter arrowsize
    -- thickness can be adjusted with optional parameter thickness
    -- if the later three are not defined default values are chosen
    if col==nil then col=color(255,255,0,255); end
    if arrowsize==nil then arrowsize=6; end
    if thickness==nil then thickness=4; end
    
    pushStyle()
        lineCapMode(PROJECT); strokeWidth(thickness)
        stroke(col); line(x0, y0, x, y)    -- position of arrow at (x,y)
        -- determine the correct angle of the vector from (x0,y0) to (x,y)
        -- best way of doing that is to shift the origin to point (x0,y0)
        pushMatrix()
            translate(x0,y0)
            local v1 = vec2(0, 0)
            local angle = math.deg(v1:angleBetween(vec2(x-x0, y-y0)))
            translate(-x0,-y0)
        popMatrix()
        -- rotate the arrow properly, and draw the arrow
        pushMatrix()
            translate(x,y)
            rotate(angle)
            strokeWidth(thickness-1)
            for i=1, arrowsize do
                line(0, 0, -3*arrowsize, -i)
                line(0, 0, -3*arrowsize, i) 
            end
            translate(-x,-y)
        popMatrix()
    popStyle()
end

Thanks sonarman that helps. I’ll plug this in and tweak it a bit and see how it works.

Crazyed, go look at the gravity example included with codea - eerie! Heh - there are only so many ways to draw an arrow :slight_smile:

Uuuuh, seems as if I was spending my time for nothing. Did not remember the gravity example … Well, was anyway a rainy day

Never for nothing. I’ve reinvented the wheel more times than I can count :slight_smile:

Sonarman, could you explain that code a little more carefully please? It seems an awfully complicated way of computing mra1 - mra2. Is the purpose to ensure that the result is in the range [0,360)? If so, there are far easier ways of accomplishing that!

Bortels, whenever the code gets complicated then I find myself returning to first principles. In particular, I find that nature usually has the best ideas on how to accomplish such things. I’d do this by simulating a magnetic field and having the ship act as a magnet. It would then attempt to align itself with the field and the action would interact with the ship’s rotational inertia in the “correct” manner.

I’ll have a play and see if I can code something.

That’s exactly the behavior I want - ideally, rotation with inertia, so the rotation speeds up, then slows to a stop. It’s just for a game, but it’s that kind of movement that tricks the spine into thinking it’s ‘real’ for some definition of real. I’d settle for a simple “rotate toward” routine - I can always fudge the inertial stuff using trig. (heh - I remember back when trig was the hard stuff)

Andrew, the purpose of that code was to eliminate problems in cases like this:

`<br />
mra1 = 5 degrees
mra2 = 355 degrees
`

mra1 - mra2 would then yield -350 which is not desirable in my application. The only reason I wrote that function was because at the time, the vec2 angular difference function did not work correctly.

The actual method I used is known as a “perp dot product.” This provides a convenient way to calculate the tangent of the smaller angle between two vectors. The angle returned is a directed angle. This is also desirable since we want to know the sign in order to increment or decrement in subsequent code. I was only trying to be helpful but I guess I missed the mark this time. Thank you.

So what you want is the difference of the angles, but wrapped into a specific interval, is that it? That’s what the mod function is for: ang % 360 returns the angle corrected to be between 0 and 360 (but not 360). (Implementations differ as to what happens to negative numbers; it seems that in Codea it does the correct thing which results always in a positive number.)

If you want something a bit more complicated, for example you might want the angle between 90 and 450 in which case you would do (a - 90)%360 + 90.

I was only trying to be helpful but I guess I missed the mark this time. Thank you.

Continue being helpful! I apologise for my over-the-top reaction - I was just a bit taken aback by the routine that you presented (I had to plug it in to octave to see what it did).

Bortels, here’s some code for you



function setup()
    stars = {}
    numstars = 1000
    local x,y
    for i = 1,numstars do
        x = math.random(-WIDTH,2*WIDTH)
        y = math.random(-HEIGHT,2*HEIGHT)
        z = math.random(10,100)
        table.insert(stars,{x,y,z})
    end
    v = vec2(0,0)
    l = 30
    p = vec2(l,0)
    o = vec2(WIDTH/2,HEIGHT/2)
    tolerence = 25
    M = 100
    dM = 5
    G = 100000
    mu = 0
    ds = 0
end

function draw()
    background(0, 0, 0)
    noStroke()
    fill(255, 255, 255, 255)
    rectMode(CENTER)
    translate(o.x,o.y)
    if CurrentTouch then
        local pp = vec2(-p.y,p.x)/p:len()
        local d = vec2(CurrentTouch.x,CurrentTouch.y)
        d = d - o
        debuga = math.atan2(d.x,d.y)
        local dd = vec2(CurrentTouch.deltaX,CurrentTouch.deltaY)
        if dd:lenSqr() < tolerence and CurrentTouch.state ~= ENDED then
            ds = ds + DeltaTime
        else
            ds = ds - DeltaTime
        end
        if d:lenSqr() > tolerence then
            local a = G * M * d:dot(pp) / (d - p):len()^3
            
            a = a - mu * v:len() * v:dot(pp)
            v = v + a * DeltaTime * pp
            p = p + v * DeltaTime
            p = p/p:len() * l
        end
    end
    for i=1,numstars do
        stars[i][1] = (stars[i][1] - math.exp(ds)*p.x/stars[i][3] + WIDTH)%(3*WIDTH) - WIDTH
        stars[i][2] = (stars[i][2] - math.exp(ds)*p.y/stars[i][3] + HEIGHT)%(3*HEIGHT) - HEIGHT
        local r = math.random(1,3)
        rect(stars[i][1],stars[i][2],r,r)
    end
    local ang = math.atan2(p.x,p.y)
    debug = ang
    rotate(180-ang/math.pi * 180)
    spriteMode(CENTER)
    sprite("Tyrian Remastered:Boss D")
end