Fix angle_to to produce signed angle

angle_to was producing the angle from +x to the difference between a,b
which is unexpected. Instead, it should produce the smallest absolute
angle between the two vectors and be signed to indicate the direction of
rotation.

By using the old angle_to implementation and modifying equal() to print
out the failures, you can see the numbers that it was producing before
didn't make much sense:

    right:angle_to(down) = 45.0
    right:angle_to(left) = 0.0
    right:angle_to(up)   = -45.0
    down:angle_to(right) = -135.0
    down:angle_to(left)  = -45.0
    down:angle_to(up)    = -90.0
    left:angle_to(down)  = 135.0
    left:angle_to(up)    = -135.0
    up:angle_to(right)   = 135.0
    up:angle_to(down)    = 90.0
    up:angle_to(left)    = 45.0

Now it produces numbers you'd expect:

    right:angle_to(down) = -90.0
    right:angle_to(left) = 180.0
    right:angle_to(up)   = 90.0
    down:angle_to(right) = 90.0
    down:angle_to(left)  = -90.0
    down:angle_to(up)    = 180.0
    left:angle_to(down)  = 90.0
    left:angle_to(up)    = -90.0
    up:angle_to(right)   = -90.0
    up:angle_to(down)    = 180.0
    up:angle_to(left)    = 90.0

See also https://stackoverflow.com/questions/21483999/using-atan2-to-find-angle-between-two-vectors

Also added tests for angle_between.
This commit is contained in:
David Briscoe 2021-06-15 11:44:34 -07:00
parent 0d8daf0536
commit 39aee9a421
3 changed files with 70 additions and 7 deletions

View File

@ -228,22 +228,31 @@ function vec2.perpendicular(a)
return new(-a.y, a.x)
end
--- Angle from one vector to another.
--- Signed angle from one vector to another.
-- Rotations from +x to +y are positive.
-- @tparam vec2 a Vector
-- @tparam vec2 b Vector
-- @treturn number angle
-- @treturn number angle in (-pi, pi]
function vec2.angle_to(a, b)
if b then
return atan2(a.y - b.y, a.x - b.x)
local angle = atan2(b.y, b.x) - atan2(a.y, a.x)
-- convert to (-pi, pi]
if angle > math.pi then
angle = angle - 2 * math.pi
elseif angle <= -math.pi then
angle = angle + 2 * math.pi
end
return angle
end
return atan2(a.y, a.x)
end
--- Angle between two vectors.
--- Unsigned angle between two vectors.
-- Directionless and thus commutative.
-- @tparam vec2 a Vector
-- @tparam vec2 b Vector
-- @treturn number angle
-- @treturn number angle in [0, pi]
function vec2.angle_between(a, b)
if b then
if vec2.is_vec2(a) then

View File

@ -45,6 +45,4 @@ project_from(out, a, b)
mirror_on(out, a, b)
reflect(out, i, n)
refract(out, i, n, ior)
angle_to(a, b)
angle_between(a, b)
--]]

View File

@ -203,6 +203,62 @@ describe("vec2:", function()
assert.is.equal(temp, vec2(-1, -2))
end)
it("finds angle from one 2-vector to another", function()
local d = {
right = vec2(1, 0),
down = vec2(0, -1),
left = vec2(-1, 0),
up = vec2(0, 1),
}
assert.is.equal(math.deg(d.right:angle_to(d.right)), 0.0)
assert.is.equal(math.deg(d.right:angle_to(d.down)), -90.0)
assert.is.equal(math.deg(d.right:angle_to(d.left)), 180.0)
assert.is.equal(math.deg(d.right:angle_to(d.up)), 90.0)
assert.is.equal(math.deg(d.down:angle_to(d.right)), 90.0)
assert.is.equal(math.deg(d.down:angle_to(d.down)), 0.0)
assert.is.equal(math.deg(d.down:angle_to(d.left)), -90.0)
assert.is.equal(math.deg(d.down:angle_to(d.up)), 180.0)
assert.is.equal(math.deg(d.left:angle_to(d.right)), 180.0)
assert.is.equal(math.deg(d.left:angle_to(d.down)), 90.0)
assert.is.equal(math.deg(d.left:angle_to(d.left)), 0.0)
assert.is.equal(math.deg(d.left:angle_to(d.up)), -90.0)
assert.is.equal(math.deg(d.up:angle_to(d.right)), -90.0)
assert.is.equal(math.deg(d.up:angle_to(d.down)), 180.0)
assert.is.equal(math.deg(d.up:angle_to(d.left)), 90.0)
assert.is.equal(math.deg(d.up:angle_to(d.up)), 0.0)
end)
it("finds angle between two 2-vectors", function()
local d = {
right = vec2(1, 0),
down = vec2(0, -1),
left = vec2(-1, 0),
up = vec2(0, 1),
}
assert.is.equal(math.deg(d.right:angle_between(d.right)), 0.0)
assert.is.equal(math.deg(d.right:angle_between(d.down)), 90.0)
assert.is.equal(math.deg(d.right:angle_between(d.left)), 180.0)
assert.is.equal(math.deg(d.right:angle_between(d.up)), 90.0)
assert.is.equal(math.deg(d.down:angle_between(d.right)), 90.0)
assert.is.equal(math.deg(d.down:angle_between(d.down)), 0.0)
assert.is.equal(math.deg(d.down:angle_between(d.left)), 90.0)
assert.is.equal(math.deg(d.down:angle_between(d.up)), 180.0)
assert.is.equal(math.deg(d.left:angle_between(d.right)), 180.0)
assert.is.equal(math.deg(d.left:angle_between(d.down)), 90.0)
assert.is.equal(math.deg(d.left:angle_between(d.left)), 0.0)
assert.is.equal(math.deg(d.left:angle_between(d.up)), 90.0)
assert.is.equal(math.deg(d.up:angle_between(d.right)), 90.0)
assert.is.equal(math.deg(d.up:angle_between(d.down)), 180.0)
assert.is.equal(math.deg(d.up:angle_between(d.left)), 90.0)
assert.is.equal(math.deg(d.up:angle_between(d.up)), 0.0)
end)
-- Do this last, to insulate tests from accidental state contamination
-- Do vec3 tests last, to insulate tests from accidental state contamination
it("converts a 2-vector to a 3-vector", function()