View Source Scurry.Polygon (Scurry v2.0.0)

Functions related to polygons and lines relevant for 2D map pathfinding.

This provides functions for;

  • line of sight between two points
  • classify polygon vertices as concave or vertex
  • intersections of lines and polygons
  • checking if points are inside/outside polygons
  • finding nearest point on a polygon and distances

Polygons are

  • A list of vertices, [{x1, y1}, {x2,y2}, ...].
  • They must not be closed, ie. last vertex should not be equal to the first.
  • They must be in clockwise order in screen coordinates, otherwise convex/concave classification will be inversed as it traverses the egdes.

order-of-vertices

Order of vertices

They must be in clockwise order in screen coordinates, otherwise convex/concave classification will be inversed as it traverses the egdes.

Here's a crude drawing of an example of the M shaped polygon used for many tests/docs.

polygon = [{0, 0}, {10, 0}, {20, 0}, {20, 20}, {10, 10}, {0, 20}] Order of vertices

Link to this section Summary

Functions

Check if a vertex is concave, convex or neither.

Split a polygon into concave and convex vertices.

Get first intersections of a line with a polygon.

Get all intersections of a line with a polygon including their type.

Checks if a line intersects a polygon.

Check if the polygon is clockwise within screen coordinates.

Check if a vertex is concave or not.

Check if a vertex is convex or not.

Check if a point is inside a polygon or not.

The opposite of is_inside?/3, provided for code readability.

Get last intersections of a line with a polygon.

Find the edge of a polygon nearest a given point

Find the point on the edge of a polygon nearest a given point.

Link to this section Functions

Link to this function

classify_vertex(polygon, at)

View Source

Check if a vertex is concave, convex or neither.

Whehter a vertex is concave or convex is defined by it pointing out - it's inner angle is less than 180 means convex and more than 180 means concave.

When testing a vertex, keep this in mind and negate appropriately depending on whether it's the boundary polygon or a hole polygon being tested.

params

Params

  • polygon, a list of {x, y} tuples outlining a polygon. This must be non-closed.
  • at, a position within polygon to check.

Return

  • :convex for a convex vertice.
  • :concave for a concave vertice.
  • :neither for a vertice that's a straight edge, ie. 180 degrees.

examples

Examples

# A vaguely M shaped polygon
iex> Polygon.classify_vertex([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 0)
:convex
iex> Polygon.classify_vertex([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 1)
:neither
iex> Polygon.classify_vertex([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 4)
:concave
Link to this function

classify_vertices(polygon)

View Source

Split a polygon into concave and convex vertices.

When doing pathfinding, there will typically be a outer polygon bounding the "world" and multiple inner polygons describing "holes". The path can only be within the outer polygon and has to "walk around" the holes.

Classifying the polygons into concave and convex gives the walkable graph.

  • The outer polygon's concave (pointing into the world) vertices should be used.
  • The holes' convex (point out of the hole, into the world) vertices should be used.

In code, this looks like

{concave, _convex} = Polygon.classify_vertices(world)

convex = Enum.reduce(holes, [], fn points, acc ->
  {_, convex} = Polygon.classify_vertices(points)
  acc ++ convex
end)

vertices = concave ++ convex

params

Params

  • polygon, a list of {x, y} tuples outlining a polygon. This must be non-closed.

Returns {list of concave vertices, list of convex}.

Three points that fall on the same line ([{0, 0}, {1, 0}, {2, 0}]) does not match neither the concave/convex definition (angle gt/lt 180 degrees) this will discard these via classify_vertex/2.

examples

Examples

# A vaguely M shaped polygon
iex> Polygon.classify_vertices([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}])
{[{1, 0.5}], [{0, 0}, {2, 0}, {2, 1}, {0, 1}]}
Link to this function

first_intersection(polygon, line)

View Source

Get first intersections of a line with a polygon.

The "opposite" of last_intersection/2.

params

Params

  • polygon a list of points ([{x, y}, {x, y}, ...]) describing a polygon.
  • line a tuple of points ({{ax, ay}, {bx, by}}) describing a line. The first tuple ({ax, ay}) is considered the head of the line and "first" in this context means nearest to that point.

Returns a {x, y} tuples indicating where the line first intersects, or nil if there's no intersection.

Link to this function

intersections(polygon, line, opts \\ [])

View Source

Get all intersections of a line with a polygon including their type.

This function basically calls Geo.line_segment_intersection/2 for every segment of the polygon against the line and filters the results to only include the list of intersection points.

params

Params

  • polygon a list of points ([{x, y}, {x, y}, ...]) describing a polygon. This must be non-closed.
  • line a tuple of points ({{ax, ay}, {bx, by}}) describing a line.
  • opts
    • :allow_points (default false) whether a on_point intersection should be considered an intersection or not. This varies from use cases. Eg. when building a polygon, points will be connected and thus intersect if true. This may not be the desired result, so false won't consider points intersections.

Returns a list of {x, y} tuples indicating where the line intersects, or [] if there's no intersections.

examples

Examples

iex> polygon = [{0, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}]
[{0, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}]
iex> line = {{1, -1}, {1, 3}}
{{1, -1}, {1, 3}}
iex> Polygon.intersections(polygon, line)
[{1.0, 0.0}]
iex> Polygon.intersections(polygon, line, allow_points: true)
[{1.0, 0.0}, {1.0, 0.5}]
Link to this function

intersects?(polygon, line)

View Source

Checks if a line intersects a polygon.

This is a bare-minimum function, and for most cases using intersections/2 will be a better choice.

params

Params

  • polygon a list of points ([{x, y}, {x, y}, ...]) describing a polygon.
  • line a tuple of points ({{ax, ay}, {bx, by}}) describing a line.

Returns true or false wether the line intersects the polygon or not.

Check if the polygon is clockwise within screen coordinates.

examples

Examples

iex> Polygon.is_clockwise?([{0, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}])
true
iex> Polygon.is_clockwise?([{0, 0}, {0, 1}, {1, 0.5}, {2, 1}, {2, 0}])
false
Link to this function

is_concave?(polygon, at)

View Source

Check if a vertex is concave or not.

params

Params

  • polygon, a list of {x, y} tuples outlining a polygon. This must be non-closed.
  • at, a position within polygon to check.

Return true or false.

Three points that fall on the same line ([{0, 0}, {1, 0}, {2, 0}]) does not match neither the concave/convex definition (angle gt/lt 180 degrees). This will return false for such a vertex.

examples

Examples

# A vaguely M shaped polygon
iex> Polygon.is_concave?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 0)
false
iex> Polygon.is_concave?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 1)
false
iex> Polygon.is_concave?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 2)
false
iex> Polygon.is_concave?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 3)
false
iex> Polygon.is_concave?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 4)
true
iex> Polygon.is_concave?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 5)
false

Check if a vertex is convex or not.

params

Params

  • polygon, a list of {x, y} tuples outlining a polygon. This must be non-closed.
  • at, a position within polygon to check.

Return true or false.

Three points that fall on the same line ([{0, 0}, {1, 0}, {2, 0}]) does not match neither the concave/convex definition (angle gt/lt 180 degrees). This will return false for such a vertex.

examples

Examples

# A vaguely M shaped polygon
iex> Polygon.is_convex?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 0)
true
iex> Polygon.is_convex?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 1)
false
iex> Polygon.is_convex?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 2)
true
iex> Polygon.is_convex?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 3)
true
iex> Polygon.is_convex?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 4)
false
iex> Polygon.is_convex?([{0, 0}, {1, 0}, {2, 0}, {2, 1}, {1, 0.5}, {0, 1}], 5)
true
Link to this function

is_inside?(polygon, point, opts \\ [])

View Source

Check if a point is inside a polygon or not.

Link to this function

is_outside?(polygon, point, opts \\ [])

View Source

The opposite of is_inside?/3, provided for code readability.

Link to this function

last_intersection(polygon, line)

View Source

Get last intersections of a line with a polygon.

The "opposite" of first_intersection/2.

params

Params

  • polygon a list of points ([{x, y}, {x, y}, ...]) describing a polygon.
  • line a tuple of points ({{ax, ay}, {bx, by}}) describing a line. The second tuple ({bx, by}) is considered the end of the line and "last" in this context means nearest to that point.

Returns a {x, y} tuples indicating where the line last intersects, or nil if there's no intersection.

Link to this function

nearest_edge(polygon, point)

View Source

Find the edge of a polygon nearest a given point

Given a point that's inside or outside a given polygon, this checks each segment of the polygon, and returns the nearest one.

params

Params

  • polygon, a list of {x, y} vertices, [{x1, y2}, {x2, y2}, ...]. This must be non-closed.
  • point a tuple {x, y} describing a point

Returns the {{x1, y1}, {x2, y2}} segment that is closest to the point.

Link to this function

nearest_point_on_edge(polygon, point)

View Source

Find the point on the edge of a polygon nearest a given point.

Given a point that's inside or outside a given polygon, this uses nearest_edge/2 to find the closest edge and then computes the point on the edge nearest the given point.

params

Params

  • polygon, a list of {x, y} vertices, [{x1, y2}, {x2, y2}, ...]. This must be non-closed.
  • point a tuple {x, y} describing a point

Returns the {x, y} on an edge of the polygon that is nearest point.