how to get the compass direction between two points ?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
gcmartijn
Party member
Posts: 136
Joined: Sat Dec 28, 2019 6:35 pm

how to get the compass direction between two points ?

Post by gcmartijn »

Hi,

I'm trying to do some simple thing but I do fail over and over I did try about 20 different things.

The idea is very simple I want to get the compass direction from the current position heading to the destination.

At the moment I use vectors using https://github.com/themousery/vector.lua.

So I have A and B and have the vector heading, with that heading I want to know the exact compass direction.
What I was using before, but that one is not 100% correct.

Code: Select all

local function getOctant(heading)
    return Math.round(16 * heading / (2 * math.pi)) % 16
    --  return Math.round(16 * heading / (2 * math.pi) + 16) % 16
end

local c = getOctant(vDirection:heading())
-- ^ the result was not 1:1 with the table below

Compass.E = 0
Compass.ENE = 1
Compass.NE = 2
Compass.NNE = 3
Compass.N = 4
Compass.NNW = 5
Compass.NW = 6
Compass.WNW = 7
Compass.W = 8
Compass.WSW = 9
Compass.SW = 10
Compass.SSW = 11
Compass.S = 12
Compass.SSE = 13
Compass.SE = 14
Compass.ESE = 15
I thought that what I did have was correct, until I made a unittest.

https://stackoverflow.com/questions/624 ... -javascrip
https://www.physicsclassroom.com/mmedia/vectors/vd.cfm
https://gamedev.stackexchange.com/quest ... -compass-d
https://stackoverflow.com/questions/368 ... conversion
https://p5js.org/reference/#/p5.Vector/heading

Code: Select all

   -- old thing
    -- local angleInRadians =
    --     math.atan2(self.vPosition.y, self.vPosition.x) - math.atan2(self.vDestination.y, self.vDestination.x)

Code: Select all

 local fromToTest = {
        -- current x, current y, dest x, dest y, compass
        {Vector(0, 0), Vector(0, 0), nil},
        {Vector(0, 0), Vector(0, 1), "N"} -- fail it say S
    }

    for _, fromTo in pairs(fromToTest) do
        local vPosition = fromTo[1]
        local vDestination = fromTo[2]
        local headingCompassDirection = fromTo[3]

        local x = vPosition.x - vDestination.x
        local y = vPosition.y - vDestination.y

        local t = (((math.acos(y / math.sqrt(x * x + y * y)) * (Math.sign(y) or 1)) * 180 / math.pi) + 360) % 360

        local arr = {
            "N",
            "NNE",
            "NE",
            "ENE",
            "E",
            "ESE",
            "SE",
            "SSE",
            "S",
            "SSW",
            "SW",
            "WSW",
            "W",
            "WNW",
            "NW",
            "NNW",
            "N"
        }
        local res = arr[Math.round(((t % 360) / 22.5) + 1)]

        lu.assertEquals(res, headingCompassDirection)
    end
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: how to get the compass direction between two points ?

Post by ivan »

The simplest way to do it is:

Code: Select all

local compass = { "E", "NE", "N", "NW", "W", "SW", "S", "SE" }
function direction(pos, target)
  local dx, dy = target.x - pos.x, target.y - pos.y
  if dx^2 + dy^2 > 0 then
    local heading = math.atan2(dy, dx)
    local octant = math.floor(8*heading/(2*math.pi) + 8 + 0.5)%8
    return compass[octant + 1]
  end
end
You can add your additional directions if you want but then change the 8 to 16.
Where a heading of 0 points EAST and it rotates counter-clockwise as the heading increases.
Note that your vector.lua library negates the result so it will rotate in the opposite direction.
Also, never call atan2 with a zero-sized vector (0, 0) because it will return 0 which means EAST.
Check out my vector tutorial at: https://2dengine.com/?p=vectors
User avatar
pgimeno
Party member
Posts: 3548
Joined: Sun Oct 18, 2015 2:58 pm

Re: how to get the compass direction between two points ?

Post by pgimeno »

atan2(p2.y, p2.x) - atan2(p1.y, p1.x) does not give you the angle of the line between p1 and p2. When you calculate the atan2 of a point, the result is the angle of that point with respect to (0, 0). The difference of two such atan2 has nothing to do with the angle between p1 and p2.

Image
Last edited by pgimeno on Sat Sep 05, 2020 1:06 pm, edited 2 times in total.
RNavega
Party member
Posts: 245
Joined: Sun Aug 16, 2020 1:28 pm

Re: how to get the compass direction between two points ?

Post by RNavega »

For the challenge I did it a different way (comparing the tangents inside a quadrant to pick the "slice"/octant, then picking the quadrant based on the sign of dx and dy).
I also changed the order of parameters in the direction function, LÖVE usually puts the position parameter first and the "origin" parameter at the end, so it's direction(targetPos, sourcePos).

Inline main.lua:

Code: Select all

function love.load()
    love.mouse.setVisible(false)
end


-- Important angles, in steps of 22.5º, for the top-right quadrant:
-- N   = 90º
-- NNE = 67.5º
-- NE  = 45º
-- ENE = 22.5º
-- E   = 0º

-- The bisections of those important angles, and their tangets will be used for picking the direction:
-- E~NNE  = (90.0 + 67.5) / 2.0 = 78.75º
-- NNE~NE = (67.5 + 45.0) / 2.0 = 56.25º
-- NE~ENE = 33.75º
-- ENE~E = 11.25º
-- All other quadrants use the same steps, the names they point to are just different.

local quadrants = {
    {'E', 'ESE', 'SE', 'SSE', 'S'},
    {'E', 'ENE', 'NE', 'NEN', 'N'},
    {'W', 'WSW', 'SW', 'SSW', 'S'},
    {'W', 'WNW', 'NW', 'NNW', 'N'},  
}

local MATH_ABS = math.abs

local lastResult = nil


function direction(target, pos)
    local dx, dy = target.x - pos.x, target.y - pos.y
    if dx == 0.0 and dy == 0.0 then return nil end
    
    local dxAbs, dyAbs = MATH_ABS(dx), MATH_ABS(dy)
    local octant = 1
    
    if dyAbs > dxAbs * 0.19891236737965800691159762264468 then -- tg(11.25º)
        if dyAbs > dxAbs * 0.66817863791929891999775768652308 then -- tg(33.75º)
            if dyAbs > dxAbs * 1.4966057626654890176011351349425 then -- tg(56.25º)
                if dyAbs > dxAbs * 5.0273394921258481045149750710641 then -- tg(78.75º)
                    octant = 5
                else
                    octant = 4
                end
            else
                octant = 3
            end
        else
            octant = 2
        end
    else
        octant = 1
    end
    
    -- This block below can be merged with the one above for a more verbose but slightly faster
    -- version with being able to return the value right there.
    if dx > 0 then
        if dy > 0 then
            return quadrants[1][octant]
        else
            return quadrants[2][octant]
        end
    else
        if dy > 0 then
            return quadrants[3][octant]
        else
            return quadrants[4][octant]
        end
    end
end


function love.mousemoved(mX, mY)
    lastResult = direction(
        {x=mX, y=mY},
        {x = love.graphics.getWidth()/2.0, y = love.graphics.getHeight()/2.0}
    )
end


function love.draw()    
    local mX = love.mouse.getX()
    local mY = love.mouse.getY()
    local oX = love.graphics.getWidth()/2.0
    local oY = love.graphics.getHeight()/2.0
    
    love.graphics.setColor(1.0, 1.0, 1.0)
    love.graphics.line(oX, oY, mX, mY)
    
    love.graphics.setColor(1.0, 1.0, 0.0)
    love.graphics.print(((lastResult and lastResult) or '<NONE>'), mX, mY, 0.0, 3.0, 3.0)
    
    love.graphics.setColor(1.0, 0.0, 0.0, 0.6)
    love.graphics.circle('line', oX, oY, 100.0)
    for x = 0, 7 do
        local angle = math.rad(22.5 * x)
        local cosAng = math.cos(angle)*100.0
        local sinAng = math.sin(angle)*100.0
        love.graphics.line(oX-cosAng, oY-sinAng, oX+cosAng, oY+sinAng)
    end
end


function love.keypressed(key)
    if key == 'escape' then
        love.event.quit()
    end
end
Attachments
compassDirectionTangents.love
(1.23 KiB) Downloaded 132 times
compassDirection.gif
compassDirection.gif (221.65 KiB) Viewed 10595 times
Last edited by RNavega on Sat Sep 05, 2020 11:42 am, edited 1 time in total.
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: how to get the compass direction between two points ?

Post by ivan »

Yes, pgimeno is correct.
a1 - a2 would be the angle between two vectors

Code: Select all

function vangle2(x, y, x2, y2)
  local a = math.atan2(y2, x2) - math.atan2(y, x)
  return (a + math.pi)%(math.pi*2) - math.pi
end
this is assuming x,y,x2,y2 are heading vectors - it will not work for points
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: how to get the compass direction between two points ?

Post by ivan »

navega, "== 0.0" is not robust.
it works for integers but not with floating point math
RNavega
Party member
Posts: 245
Joined: Sun Aug 16, 2020 1:28 pm

Re: how to get the compass direction between two points ?

Post by RNavega »

Hm, if it were some arbitrary float number then I would agree, but it's zero. I think it should be safe, even if either dx or dy are +0.0 or -0.0.

If you remove that check, which you can do, then whenever the source and destination are at the same position the result will be whatever passes the tests (which is "W", AKA quadrants[4][1]). Maybe it's better to have it returning this direction than that nil, anyway.
User avatar
pgimeno
Party member
Posts: 3548
Joined: Sun Oct 18, 2015 2:58 pm

Re: how to get the compass direction between two points ?

Post by pgimeno »

Yes, I believe 0.0 is safe here. Equality comparison is not trivial and needs to be examined case by case. In this case it is.

That said, I would prefer to return an arbitrary angle here (like 0.0) rather than nil. Furthermore, I'm pretty sure that the big branching factor adds a performance penalty with respect to a pure atan2 + lookup implementation. See for example the benchmarks at https://love2d.org/forums/viewtopic.php ... 09#p218409
gcmartijn
Party member
Posts: 136
Joined: Sat Dec 28, 2019 6:35 pm

Re: how to get the compass direction between two points ?

Post by gcmartijn »

Wow thanks for the feedback ! :awesome:

I will give it a try.
And yes I really need to read this: https://2dengine.com/?p=vectors and maybe I need another vector lib.

I did see but don't understand why he did the reverse -

Code: Select all

-- get the heading (direction) of a vector
function vector:heading()
  return -math.atan2(self.y, self.x)
end
I will mention all your names in the credit list :)
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: how to get the compass direction between two points ?

Post by ivan »

pgimeno wrote: Sat Sep 05, 2020 1:25 pm Yes, I believe 0.0 is safe here. Equality comparison is not trivial and needs to be examined case by case. In this case it is.
Nah, it's not a good check. Positions in games are rarely integers and usually change in floating point increments especially when working with delta. Checking if (target - position) == 0 is rarely going to be true.

I'm not an expert in floating point math but he uses multiplication/division statements
which could result in weird behavior (when dxAbs is close to zero) :

Code: Select all

dxAbs = 1e-323
assert(dxAbs > 0) -- true
dxAbs = dxAbs*0.19891236737965800691159762264468
assert(dxAbs > 0) -- false
Post Reply

Who is online

Users browsing this forum: No registered users and 18 guests