ISSUES V2.0: angles still suck

Okay, so, I’ll probably end up beating my face into a wall until the wall caves in, but, I’m having more trouble understanding angles(yay!). In the calcshiploc() function I commented out some code which does not work to update the location (to see how not-working it is, simply I uncomment it and comment out the “shiploc = shipVelocity:rotate(anglerad) + shiploc” line. I’m sure either @andrew_stacey or @ignatz will promptly make me feel like a total buffoon this time around (as they so often do, but, I’m not complaining, I appreciate the self-scorn they give me the power to apply, love you guys). But really, the day I understand angles will be the day pigs fly and whales grow legs…

function setup()
    stroke(255, 0, 0, 139)
    smooth()
    textMode(CORNER)
    lineCapMode(ROUND)
    displayMode(FULLSCREEN)
    supportedOrientations(LANDSCAPE_LEFT)
    lasso, originid = nil, nil
    simpveloc=3
    shipVelocity = vec2(simpveloc,0)
    shiploc = vec2(WIDTH/2, HEIGHT/2)
    anglerad = math.pi/2
    dots = {}
    numdots = math.deg(math.pi*.5)
    dotang=0
    b=0
    dotfx= true
    controlswitch = 3
    --768 width
    --1024 height
    --256 difference
    drawguides = false
    guidevec= vec2(0,0)
end

function draw()
    background(0,0,0,255)
    calcshiploc()
    fxdraw()
    drawship()
end

function drawship()
    pushMatrix()
    strokeWidth(1)
    stroke(0, 255, 0, 255)
    fill(0,0,0,0)
    translate(shiploc.x,shiploc.y)
    if drawguides==true or lasso == nil then
    ellipse(0,0,60,60)
    ellipse(0,0,240,240)
    end
    rotate(math.deg(anglerad)-90)
    sprite("Space Art:Red Ship", 0,0, 30)
    popMatrix()
    strokeWidth(3)
    stroke(0,255,0,255)
    fill(0, 0, 0, 255)
    rect(768,0-1,256,768+1.5)
end

function fxdraw()
    if drawguides then
        dist = guidevec:dist(shiploc)
        if dist >30 and dist <120 then
            strokeWidth(1)
            stroke(0, 255, 6, 255)
            line(guidevec.x-40, guidevec.y, guidevec.x+40, guidevec.y)
            line(guidevec.x, guidevec.y-40, guidevec.x, guidevec.y+40)
            fill(0,0,0,0)
            ellipse(guidevec.x, guidevec.y, 60,60)
            ellipse(guidevec.x, guidevec.y, dist*2,dist*2)
        end
    end
    if lasso ~= nil then
        stroke(255, 0, 0, 138)
        strokeWidth(3)
        line(lasso.x,lasso.y, shiploc.x,shiploc.y)
        if b<lassolength then
            calcdots()
        end
        strokeWidth(10)
        pushMatrix()
        translate(lasso.x, lasso.y)
        for i=1, #dots do
            if i%2 == 0 then
                rotate(dotang)
                ellipse(dots[i].x,dots[i].y, 5)
                rotate(-dotang)
            else
                rotate(-dotang)
                ellipse(dots[i].x,dots[i].y, 5)
                rotate(dotang)
            end
        end
        dotang=dotang+.05
        popMatrix()
    end
    
    fill(255,255,255,255)
    text(math.floor(1/DeltaTime), WIDTH/2-15, HEIGHT-30)
    text(math.floor(ElapsedTime), WIDTH/2-15, HEIGHT-50)
end
    

function touched(touch)
    if controlswitch == 1 then
        if touch.state == BEGAN and originid == nil then
            initlasso(touch)
        elseif touch.state == ENDED and touch.id == originid then
            lasso = nil
            originid = nil
            dots = nil
            dots = {}
        end
    elseif controlswitch == 2 then
        if touch.state == BEGAN and originid == nil then
            initlasso(touch)
        elseif touch.state == BEGAN and originid ~= nil and b==lassolength then
            lasso=nil
            originid=nil
            dots = nil
            dots = {}
            initlasso(touch)
        end
    elseif controlswitch == 3 then
        guidevec= vec2(touch.x,touch.y)
        if touch.state == BEGAN and originid == nil and touch.x < 768 then
            drawguides = true
            originid = touch.id
        elseif touch.state == ENDED and originid == touch.id and (vec2(touch.x,touch.y):dist(shiploc) < 120 and vec2(touch.x,touch.y):dist(shiploc) >30) then
            dots = nil
            dots = {}
            drawguides = false
            initlasso(touch) 
            originid = nil
        elseif touch.state == ENDED and originid == touch.id then
            drawguides = false
            originid = nil
        end
    end
end

function initlasso(t)
    b=0
    lassolength = vec2(t.x,t.y):dist(shiploc)
    if lassolength < 30 or lassolength > 120 then
        lasso = nil
        originid = nil
        return
    end
    originid = t.id
    lasso = vec2(t.x,t.y)
    local circumf = 2*math.pi*lassolength
    angleaddrad = 2*math.pi*simpveloc/circumf
    local offset = lasso - shiploc
    local anglebet = math.atan2(offset.y,offset.x)
    local tangent = anglebet - (math.pi/2)
    if (anglerad - anglebet)%(2*math.pi) > math.pi then
        anglerad = tangent
    else
        anglerad = tangent + math.pi
        angleaddrad = -angleaddrad
    end
end

function calcshiploc()
    if lasso ~= nil then
        anglerad = anglerad+ angleaddrad
    end
    shiploc = shipVelocity:rotate(anglerad) + shiploc
    --if lasso ~= nil then
    --    local offset = lasso - shiploc
    --    local anglebet = math.atan2(offset.y,offset.x)
    --    anglebet=anglebet+angleaddrad
    --    shiploc.x = (math.cos(anglebet)*lassolength)+shiploc.x
    --    shiploc.y = (math.sin(anglebet)*lassolength)+shiploc.y
    --else
    --    shiploc = shipVelocity:rotate(anglerad) + shiploc
    --end
end

function calcdots()
    if dotfx == true then
        b=b+lassolength/math.sqrt(lassolength*4)
        if b>lassolength then
            b= lassolength
        end
    else
        b=lassolength
    end
    
    for i=0, numdots do
        --dots[i] = vec2(lasso.x+(b*math.cos(math.rad(i*(360/numdots)))), lasso.y+(b*math.sin(math.rad(i*(360/numdots))))) (for if i change my mind about animations)
        if i%2 == 0 then
            b=b+3
            dots[i] = vec2((b*math.cos(math.rad(i*(360/numdots)))),(b*math.sin(math.rad(i*(360/numdots)))))
            b=b-3
        else
            dots[i] = vec2((b*math.cos(math.rad(i*(360/numdots)))),(b*math.sin(math.rad(i*(360/numdots)))))
        end
    end
    
end

function turnaround()
    if lasso~=nil then
    anglerad = anglerad + math.pi
    if anglerad > 2*math.pi then
        anglerad = anglerad-(2*math.pi)
    end
    angleaddrad = -angleaddrad
    end
end

P.S. I’m really proud of some of the purdy effects I put on there, so, you could comment on those too and I wouldn’t complain casually whistles

P. double-S. It’s meant to be run in LANDSCAPE mode. Is there a way to force it to start in landscape? I have supported orientations set to landscape, but if you press the run button while codea is in portrait it’ll still start in portrait, much to my chagrin. Is there a fix for that? If so, that’d be much appreciated to alleviate my minor annoyance.

@Monkeyman32123 first version seemed to work pretty well for me but I’m guessing you had issues with the ship turning anticlockwise rather than clockwise.

My snake tutorial may provide some pointers - I don’t have time to go through your code at the moment though
https://bitbucket.org/TwoLivesLeft/core/wiki/Step%20by%20step%20projects

One thing - there is the math.rad() and math.deg() functions to save you doing a manual conversion

At first test it does seem to work well, but the idea is that, when the “lasso” is made the ship goes either anti clockwise or clockwise depending on the direction it is already moving. At first test it seems to work but then it begins to get weird and go awry. And I cannot figure it out for the life of me. I’m taking calculus but I will NEVER (NEVER, I SAY!) be able to grasp angles and vectors, they confuse me to no end.
A good way to see how it doesn’t work is to tap repeatedly in one spot; rather than the ship continuing in what would be the “logical” direction with each new lasso, it switches direction each time.

@Monkeyman32123 - you may be having the same problem as this

http://twolivesleft.com/Codea/Talk/discussion/4581/need-some-help-with-homing-missiles

Looks like a similar problem, yeah, I just didn’t understand their solution. I’m really stupid with angles. I guess I just don’t understand all y’all’s circular logic :stuck_out_tongue:
I know there’s some stupidly simple fix, I just can’t wrap my brain around it.

Ok, so I tried a ton of things and I sort-of fixed the whole “tap in the same place and it switches” thing but now it just in general doesn’t go the right way still…I DONT UNDERSTAND single tear rolls out as a furiously scrawl angle-y stuff on a paper

@Monkeyman32123 - if it makes you feel any better, I’ve had similar problems. Put these lines at the bottom of calcshiploc, just before the very last line that changes shiploc, they should fix things.

if angle<-180 then angle=angle+360 elseif angle>180 then angle=angle-360 end 
anglerad=math.rad(angle)

By the way, in draw, you don’t need to reverse the rotation and translation when you’re done drawing. Use pushMatrix and popMatrix, they do it for you, like so

    pushMatrix() --store current situation
    translate(shiploc.x,shiploc.y)
    rotate(angle-90)
    sprite("Space Art:Red Ship Bank Left", 0,0, 30)
    popMatrix()  --put it all back as it was before

@Monkeyman32123:

I’m not sure if this is relevant (I haven’t completely grasped your code), but have did you see the vec2:angleBetween(v) method in the “vectors” part of the Codea reference?

I did see that but I already don’t understand angles very well so I figured id stick to the simplest functions possible, for my own benefit. I’m sure it’s extremely relevant somewhere, I just don’t know how to use it XD I could go through and attempt to comment the code to make it easier for you guys to help me, if you think you could give me more specific help that way, because I need as much help as I can get XP

@Ignatz thanks for the help, but, I’m still having the same issue even when inserting that line (and I’ve already tried similar things to no avail). The issue only occurs once you’ve played with it a bit (at first it seems normal, then you’ll notice odd behaviour[it just doesn’t always seem to go the right way], I really don’t know the words to describe the issue).
That said, the push matrix thing was super ridiculously helpful, thank you. You’re a life saver, I’ve been manually rotating and unrotating SO MANY THINGS

@Monkeyman32123 - if you just rotated your ship by one extra degree at each draw, it would go round and round very happily.

The problem, I think, is the atan function. This is because the tan function repeats itself every 180 degrees. This means that if the tan value is (say) 0.36, the angle could be either 20 or 200.

When you use atan, it always gives you an angle between -180 and +180. So if tan is 0.36, atan will always give you an angle of 20.

So what do you do if the angle is actually 200? Atan is going to use 20, and turn your ship the wrong way round.

The answer is to realise that an angle of 200 is the same as an angle of -160 (you are just turning the other way). And atan will give the correct answer for a figure of -160, because it is between -180 and +180. So if our angle is greater than 180, we need to subtract 360 degrees to get the negative equivalent.

Similarly,if your angle is less than -180 degrees, you need to add 360 degrees.

But only if you’re using atan.

@Monkeyman32123 - post your code again, maybe we can help

The problem, I’m quite sure, is in this bit here

if anglebet-anglerad >  0 then
            anglerad = tangent
else
            anglerad = tangent2
            angleaddrad = -angleaddrad
end

What it need to do is decide, based on the angle of the ship and the angle of the lasso, what direction (clockwise or anti-clockwise) the ship will rotate about the lasso. But I cannot for the life of me find a logical comparison to do so

And @Ignatz - this bit of code was used to make sure I get a value between 0 and 2*math.pi for atan

preangle = (math.atan((offsety)/(offsetx)))
        if offsetx == 0 and offsety > 0 then
            anglebet = math.pi/2
        else
            if offsetx <= 0 and offsety > 0 then
                anglebet = math.pi + preangle
            elseif offsetx < 0 and offsety <= 0 then
                anglebet = math.pi + preangle
            elseif offsetx >=0 and offsety < 0 then
                anglebet = (2*math.pi) + preangle
            else
                anglebet = preangle
            end
        end

I tested it and it seemed to work, but I suppose that could also be a source of error. Heck, any of it could be. I don’t even know anymore

@Monkeyman32123:
Ignatz is right that atan will keep you stuck because it returns the angle between two lines, not two directions.

Regarding:

I did see that but I already don’t understand angles very well so I figured id stick to the simplest functions possible, for my own benefit.

I’m not sure throwing in atan is the “simplest function possible” :stuck_out_tongue:

How about trying:

  anglerad = vec2(1, 0):angleBetween(lasso-shiploc)

instead of all those calculations?

Or maybe what you want (I cannot quite tell from your code) is:

  anglerad = shipVelocity:angleBetween(lasso-shiploc)

Atan is simpler to me, I learned all about it in school and I understand it pretty well, for the most part :stuck_out_tongue: not the simplest function, but the only one I understand. Maybe I do want your super simple line…I’ll look into it. But here, for my own selfish benefit, is a completely commented version of my code. Behold it’s majesty (or lack thereof)!

function setup()
    strokeWidth(10)
    stroke(255, 0, 0, 139)
    lasso, originid = nil, nil
    --the velocity of the ship simplified and in vector form, both have their uses
    simpveloc = math.pi
    shipVelocity = vec2(simpveloc,0)
    displayMode(FULLSCREEN)
    --starting location and angle for the ship; dont ask why i chose 45 degrees, man, its just how i do
    shiploc = vec2(WIDTH/2, HEIGHT/2)
    angle = 45
    anglerad = math.pi/4
end

function draw()
    --make sure the last drawing is covered up, courtesy of Ignatz (hes going to have more credit than me in these comments, and i greatly appreciate it)
    background(0,0,0,255)
    --call the mystical ship location calculating faeries (the function itself is boring, so i spiced up its description)
    calcshiploc()
    --draw the lasso's line. 
    if lasso ~= nil and originid ~= nil then
    line(lasso.x,lasso.y, shiploc.x,shiploc.y)
    end
    strokeWidth(0)
    --that ellipse is just there to show the area where you may not put the lasso's origin (more on that in calcshiploc())
    ellipse(shiploc.x,shiploc.y,60,60)
    --havent done the push and pop matrix thing yet, sorry, im too focused on that other thing that doesnt work. ill get there. till then, manual rotation
    translate(shiploc.x,shiploc.y)
    rotate(angle-90)
    sprite("Space Art:Red Ship", 0,0, 30)
    rotate(-angle+90)
    translate(-shiploc.x, -shiploc.y)
    strokeWidth(10)
end

function touched(touched)
    --Decide if the lasso should or should not be being drawn (that sentence sucked)
    --makes sure only the original touch affects the lasso, and that there can be only one lasso (FUTURE MULTI-TOUCH FUNCTIONALITY YOU ASK? why yes, thank you for asking, im so glad you observed that)
    if touched.state == BEGAN and originid == nil then
        originid = touched.id
        lasso = vec2(touched.x,touched.y)
        --that variable right below here is there to make sure the lasso is only set up once
        update = true
    elseif touched.state == ENDED and touched.id == originid then
        lasso = nil
        originid = nil
        update = false
    end
end

function calcshiploc()
    --if its just now being touched then do all of the fancy stuff
    if lasso ~= nil and originid ~= nil and update == true then
        --calculate lasso length and nullify (or shall i say "nil-ify") the lasso if it is too short
        lassolength = math.sqrt(((lasso.x-shiploc.x)^2)+((lasso.y-shiploc.y)^2))
        if lassolength < 30 then
            lasso = nil
            originid = nil
            return
        end
        --calculate the circumference of the circle
        circumf = 2*math.pi*lassolength
        --calculate the number of degrees rotation per draw necessary to move at simpveloc (the ships velocity) [this took me forever to figure out; i was proud]
        angleadd = 360/(circumf/simpveloc)
        angleaddrad = (angleadd/360)*(2*math.pi)
        --calculate the offset in both the x and y axes from the ship to the lasso
        offsetx = -shiploc.x+lasso.x
        offsety = -shiploc.y+lasso.y
        --get the angle (in radians) between the ship and the lasso. returns a value from 0- (2*math.pi) [not the best code, i realise, but its easy for me to understand, and it works, so, hey, lay off the scoffing]
        preangle = (math.atan((offsety)/(offsetx)))
        if offsetx == 0 and offsety > 0 then
            anglebet = math.pi/2
        else
            if offsetx <= 0 and offsety > 0 then
                anglebet = math.pi + preangle
            elseif offsetx < 0 and offsety <= 0 then
                anglebet = math.pi + preangle
            elseif offsetx >=0 and offsety < 0 then
                anglebet = (2*math.pi) + preangle
            else
                anglebet = preangle
            end
        end
        --find the two tangent angles of the lasso, one for clockwise and one for anti-clockwise
        --anti-clockwise
        tangent = anglebet - (math.pi/2)
        if tangent < 0 then
            tangent = tangent + (math.pi*2)
        end
        --clockwise
        tangent2 = anglebet + (math.pi/2)
        if tangent2 > (math.pi*2) then
            tangent2 = tangent2 - (math.pi*2)
        end
        print(tangent..": ab || "..tangent2..": ar")
        --supposed to find [and confirm] which direction (clock or anti-clock) the ship should go based on the direction the ship is going and the angle between the ship and the lasso. currently doesnt work, but this one works best of all of my attempts, so im calling it a personal victory
        if math.abs(anglerad - tangent) < math.abs(tangent2- anglerad) then
            anglerad = tangent
        else
            anglerad = tangent2
            angleaddrad = -angleaddrad
        end
        --set the angle in degrees to match its radian counterpart
        angle = 360*(anglerad/(math.pi*2))
        --set update to false to let the program know that the lasso has been made and doesnt need to be recalculated every draw
        update = false
    elseif update == false and lasso ~= nil and originid ~= nil then
        --if not setting up the lasso (aka it isnt just now being touched), then this is run, which keeps the ship moving in nice pretty circles, unless the velocity is too high, then theyre nice pretty eggs, but considering i never plan to go above 10 velocity, no sweat, plus eggs are healthy, so theres that.
        anglerad = anglerad+ angleaddrad
        angle = 360*(anglerad/(math.pi*2))
    end
    --fix the angles to be pretty numbers that fit in the unit circle courtesy of Ignatz
    if angle<0 then angle=angle+360 elseif angle>360 then angle=angle-360 end 
anglerad=math.rad(angle)
    --update the ships location based on its rotation or lack thereof
    shiploc = shipVelocity:rotate(anglerad) + shiploc
end

P.S. @daveedvdv I really appreciate your help, I do, but everything you say is soaring right over my head. Your knowledge is, as of now, beyond my grasp. I am trying to learn, but I’m nothing but a fool as of yet. I am looking into all of your suggestions, and then as I sip my tea and munch on a scone, I realize that I just don’t get it, and I scratch my head in awe at how it baffles me, and I continued to attempt to unlock your code’s secrets, but its knowledge eludes my simple mind

@Monkeyman32123 - I just wrote a post about this, because it has come up more than once. Maybe it will help you a little

http://coolcodea.wordpress.com/2014/02/19/151-when-2d-rotations-go-weird/

@Monkeyman32123 - the fix I gave you before wasn’t accurate. Take it out, and put this code higher up where you calculate preangle

local prevAngle=angle
preangle = (math.atan((offsety)/(offsetx)))
if preangle-prevAngle<-math.pi then preangle=preangle+2*math.pi
elseif preangle-prevAngle>math.pi then preangle=preangle-2*math.pi end

also, you can replace this

lassolength = math.sqrt(((lasso.x-shiploc.x)^2)+((lasso.y-shiploc.y)^2))

with this

lassolength =lasso:dist(shiploc)

Quick comment: always use atan2(y,x) instead of atan(y/x). It fixes the ambiguity.

@Ignatz Sorry, but your post confuses two issues and is, to be blunt, wrong. I’ll try to write up something later.

@Andrew_Stacey - this code using atan2 produces the same error

function setup()
    for i=0,360,60 do
        x=math.cos(math.rad(i))
        y=math.sin(math.rad(i))
        print(i,math.deg(math.atan2(y,x)))
    end
end