Maths query

Hi All - anyone know of a conversion of atan() to atan2() ? Apparently atan2() of an angle calculation returns -pi < 0 < pi.

@Bri_G Does one of there work for you.

function setup()
    ang=25
    
    print(math.atan(ang))
    print(math.atan(math.deg(ang)))
    print(math.atan(math.rad(ang)))
    
    print(math.atan2(ang))
    print(math.atan2(math.deg(ang)))
    print(math.atan2(math.rad(ang)))
end

@Bri_G Or something like this.

function setup()
    print(math.tan(math.rad(45)))
    print(math.atan2(1)*180/math.pi)
end
  • math.atan(y/x) returns the angle from 0 to pi of the line with gradient y/x, so (x,y) and (-x,-y) return the same angle.
  • math.atan2(y,x) returns the angle from -pi to pi of the ray through the point (x,y) so (x,y) and (-x,-y) return angles that differ by pi. (Note the order of the arguments is y,x.)

@dave1707 - thanks for the feedback, tried using them and slight improvement with the following line of code :


angle = math.deg(math.atan(dy,dx))

Didn’t produce the effect I wanted - now beginning to think it is language problem javascript to Lua. Just trying to duplicate some demo code and my Javascript is a little rusty - but I think it represents angles in a different form to Lua. Now investigating this. Will most what I am doing once I resolve it.

@LoopSpace - thanks for the feedback that’s the best definition I’ve seen so far and fits what I am trying to do.

p.s. Is atan2() in the reference section for Lua in the Codea reference? Didn’t see it.

@Bri_G If you describe what you’re trying to do, I can advise better on which to use (though the answer is most likely to be atan2). I know a little javascript so if you post the original code, I may be able to help translate it. The atan/atan2 issue is different to radians vs degrees, but it may be that the latter is muddying the waters a bit (Codea isn’t consistent on when it uses degrees and when radians).

@Bri_G Run this. I don’t see a difference between atan and atan2.

function setup()
    print("atan functions")
    print(math.atan(5,5)*180/math.pi)       -- 1st quadrant
    print(math.atan(-5,5)*180/math.pi)      -- 2nd quadrant
    print(math.atan(-5,-5)*180/math.pi)      -- 3rd quadrant
    print(math.atan(5,-5)*180/math.pi)      -- 4th quadrant

    print("atan2 functions")    
    print(math.atan2(5,5)*180/math.pi)
    print(math.atan2(-5,5)*180/math.pi)
    print(math.atan2(-5,-5)*180/math.pi)
    print(math.atan2(5,-5)*180/math.pi)
end

@LoopSpace - no problem with that but an entry in the reference reflecting this would be useful and highlight the importance of the number of parameters.

@Bri_G Point taken on the reference. Tagging @Simeon on this.

With regard to your code, I recommand that you replace:

    dx = xin - xpos[sn]
    dy = yin - ypos[sn]
    angle = math.atan2(dy, dx)
    xpos[sn] = xin - math.cos(angle) * segLength
    ypos[sn] = yin - math.sin(angle) * segLength

with

    dir = vec2(xin - xpos[sn], yin - ypos[sn])
    dir = dir:normalize() * segLength
    xpos[sn] = xin - dir.x
    ypos[sn] = yin - dir.y

Calculating the inverse tangent is expensive, computationally speaking, and if you’re only going to take its cosine and sine afterwards then there’s no need. Normalising a vector is much cheaper.

@dave1707 @LoopSpace - my code above tries to imitate Follow3.html - I have a number of parameters set up to view a few of the variables involved some seem to change rapidly whilst others don’t appear to change suggesting I haven’t got the correct logic. Also each segment is either overwriting the previous one or is not drawn at all and the vertical y properties seem to be ignored with the exception of the touch position.

The answer is probably very simple and obvious - but at the moment I can not see it.

@dave1707 - would you expect that?

OK, I’ll post my code - it’s trying to duplicate an example which I think is Javascript as it runs in a `PC window. My Codea post is below and the link to the demo is below it. I was hoping to use this to enable drawing of random strings on the screen - like scribbling across the screen, so I was interested in the curve and linking code to see if I could reproduce and modify to my needs:


displayMode(FULLSCREEN)
function setup()
    --
    parameter.watch("xpos[1]")
    parameter.watch("ypos[1]")
    parameter.watch("xpos[19]")
    parameter.watch("ypos[19]")
    parameter.watch("angle")
    xpos = {} -- 20
    ypos = {} -- 20
    for loop = 1, 20, 1 do
        xpos[loop] = 0
        ypos[loop] = 0
    end
    segLength = 32
    angle = 0
    xin, yin = 0,0
    skip = false
end

function draw() 
    background(0)
    dragSegment(1,xin,yin)
    if not skip then
        for i = 1, #xpos-1, 1 do
            dragSegment(i, xpos[i], ypos[i])
        end
    end
end

function touched(t)
    --
    if t.state == BEGAN then
        xin,yin = t.x, t.y
        skip = true
    elseif t.state == MOVING then
        xin,yin = t.x, t.y
        skip = true
    elseif t.state == ENDED then
        xin,yin = 0,0
        skip = true
    end
end

function dragSegment(sn, xin, yin)
    --
    dx = xin - xpos[sn]
    dy = yin - ypos[sn]
    angle = math.atan2(dy, dx)
    xpos[sn] = xin - math.cos(angle) * segLength
    ypos[sn] = yin - math.sin(angle) * segLength
    segment(xpos[sn], ypos[sn], angle)
end

function segment( x,  y,  a)
    --
  pushMatrix()
    strokeWidth(9)
    stroke(140, 143, 160, 100)
    translate(x, y)
    rotate(a)
    line(0, 0, segLength, 0)
  popMatrix()
end

and here is the link -

follow3

@dave1707 In lua5.3 it seems that they’ve made a very sensible decision regarding math.atan and math.atan2:

math.atan (y [, x])

Returns the arc tangent of y/x (in radians), but uses the signs of both arguments to find the quadrant of the result. (It also handles correctly the case of x being zero.)

The default value for x is 1, so that the call math.atan(y) returns the arc tangent of y.

(source https://www.lua.org/manual/5.3/manual.html#pdf-math.atan )

I’m presuming that @Simeon has made math.atan2 an alias of math.atan for backwards compatibility.

So math.atan() with a single argument computes the inverse tangent of its argument, while math.atan() with two arguments takes into account the quadrant.

This means that the crucial difference between the two is in the number of arguments that are supplied.

@Bri_G Try this. The simple answer was that angle expects to be in degrees but all the atan2 values are in radians. Multiplying the angle by 180/pi converts radians to degrees.

displayMode(FULLSCREEN)
    
function setup()
    x,y={},{}
    for z=1,20 do
        x[z]=0
        y[z]=0
    end
    segLength = 18
    strokeWidth(9);
    stroke(255, 100);
    tx,ty=0,0
end

function draw()
    background(0);
    dragSegment(1, tx, ty);
    for i=1,#x-1 do
        dragSegment(i+1, x[i], y[i]);
    end
end

function touched(t)
    if t.state==BEGAN or t.state==MOVING then
        tx=t.x
        ty=t.y
    end
end

function dragSegment(i,xin,yin)
    dx = xin - x[i]
    dy = yin - y[i];
    angle = math.atan2(dy, dx)
    x[i] = xin - math.cos(angle) * segLength;
    y[i] = yin - math.sin(angle) * segLength;
    segment(x[i], y[i], angle);
end

function segment(x,y,a)
    pushMatrix()
    translate(x,y)
    rotate(a*180/math.pi)
    line(0,0,segLength,0)
    popMatrix()
end

@dave1707 - thanks for the correction, interesting how such a subtle change can make or break it. Thought it was down to format initially but struggled to resolve.

Also interesting that code with a terminal ; on lines is accepted?

Thanks again

@Bri_G After I got your example working and I was playing with it, I thought it looked familar. I looked thru my code and found this. I’ll see if I can find when I first posted this. It must have been early in my coding years because I have displayMode inside of setup. I did that in my earlier years of Codea. Now I put it before setup.

--snake1

function setup()
    displayMode(FULLSCREEN)
    tab={}
    x=WIDTH/2
    y=HEIGHT/2
    sx=0
    sy=0
    length=150
end

function draw()
    background(40,40,50)     
    fill(255,0,0)
    text("Drag your finger around the screen",WIDTH/2,HEIGHT-50)
    x = x + sx
    y = y + sy
    if #tab<length then
        table.insert(tab,1,vec2(x,y))
    else
        table.insert(tab,1,vec2(x,y))
        table.remove(tab,#tab)
    end
    for a,b in pairs(tab) do
        if a==1 then
            ellipse(b.x,b.y,20,20)
        else
            ellipse(b.x,b.y,10,10)
        end
    end
end

function getDirection()
    dx=CurrentTouch.x-x
    dy=CurrentTouch.y-y
    h=math.sqrt(dx*dx+dy*dy)
    sx=dx/100
    sy=dy/100
end

function touched(t)
    getDirection()
end