Planar Shape Construction

Basic representations of common two-dimensional shapes:

Rectangle :
An axis-aligned rectangle.
Circle :
A planar circle with a defined origin and radius.

Also contains convenience methods for creating complex polygons:

arc() :
a semi-circular arc of points
bezier() :
a bezier curve with specified control points
class petrify.shape.Circle(origin, radius, segments)[source]

Bases: petrify.plane.Polygon2

Approximates a perfect circle with a finite number of line segments:

>>> circle = Circle(Point2.origin, 1, 5)
clockwise()

Returns True if the points in this polygon are in clockwise order:

>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]).clockwise()
True
>>> Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]).clockwise()
False
contains(p)

Tests whether a point lies within this polygon:

>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> tri.contains(Point2(1.0, 0.5))
True
>>> tri.contains(Point2(0.5, 1.5))
False
inverted()

Reverse the points in this polygon:

>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> tri.inverted()
Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])
inwards(edge)

Finds the normalized Ray2 facing inwards for a given edge:

>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> tri.inwards(tri.segments()[0])
Vector2(0.0, 1.0)
is_convex()

Return True if the polynomial defined by the sequence of 2D points is ‘strictly convex’:

>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> tri.is_convex()
True
>>> indent = Polygon2([              Point2(0, 0),                    Point2(10, 0),                   Point2(5, 5),                    Point2(10, 10),                  Point2(0, 10)                ])
>>> indent.is_convex()
False

Note

“strictly convex” is defined as follows:

  • points are valid
  • side lengths are non-zero
  • interior angles are strictly between zero and a straight angle
  • the polygon does not intersect itself
offset(amount, tolerance=0.0001)

Finds the dynamic offset of a polygon by moving all edges by a given amount perpendicular to their direction:

>>> square = Polygon2([Point2(0, 0), Point2(0, 1), Point2(1, 1), Point2(1, 0)])
>>> square.offset(-0.1)
Polygon2([Point2(0.1, 0.1), Point2(0.1, 0.9), Point2(0.9, 0.9), Point2(0.9, 0.1)])
>>> square.offset(0.1)
Polygon2([Point2(-0.1, -0.1), Point2(-0.1, 1.1), Point2(1.1, 1.1), Point2(1.1, -0.1)])
>>> square.offset(10) is None
True

Note

This is currently a naive implementation that does not properly handle non-local intersections that can split the polygon.

simplify(tolerance=0.0001)

Remove any duplicate points, within a certain tolerance:

>>> Polygon2([Point2(1, 1), Point2(2, 0), Point2(0, 0), Point2(1, 1)]).simplify()
Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])

Returns None if the resulting simplification would create a point:

>>> Polygon2([Point2(1, 1), Point2(2, 0), Point2(0, 0), Point2(1, 1)]).simplify(100) is None
True
to_clockwise()

Converts this polygon to a clockwise one if necessary:

>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]).to_clockwise()
Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]).to_clockwise()
Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
to_counterclockwise()

Converts this polygon to a clockwise one if necessary:

>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]).to_counterclockwise()
Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])
>>> Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]).to_counterclockwise()
Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])
class petrify.shape.Rectangle(origin, size)[source]

Bases: petrify.plane.Polygon2

An axis-aligned rectangle with a point of origin and a vector size:

>>> square = Rectangle(Point2.origin, Vector2(1, 1))
clockwise()

Returns True if the points in this polygon are in clockwise order:

>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]).clockwise()
True
>>> Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]).clockwise()
False
contains(p)

Tests whether a point lies within this polygon:

>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> tri.contains(Point2(1.0, 0.5))
True
>>> tri.contains(Point2(0.5, 1.5))
False
inverted()

Reverse the points in this polygon:

>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> tri.inverted()
Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])
inwards(edge)

Finds the normalized Ray2 facing inwards for a given edge:

>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> tri.inwards(tri.segments()[0])
Vector2(0.0, 1.0)
is_convex()

Return True if the polynomial defined by the sequence of 2D points is ‘strictly convex’:

>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> tri.is_convex()
True
>>> indent = Polygon2([              Point2(0, 0),                    Point2(10, 0),                   Point2(5, 5),                    Point2(10, 10),                  Point2(0, 10)                ])
>>> indent.is_convex()
False

Note

“strictly convex” is defined as follows:

  • points are valid
  • side lengths are non-zero
  • interior angles are strictly between zero and a straight angle
  • the polygon does not intersect itself
offset(amount, tolerance=0.0001)

Finds the dynamic offset of a polygon by moving all edges by a given amount perpendicular to their direction:

>>> square = Polygon2([Point2(0, 0), Point2(0, 1), Point2(1, 1), Point2(1, 0)])
>>> square.offset(-0.1)
Polygon2([Point2(0.1, 0.1), Point2(0.1, 0.9), Point2(0.9, 0.9), Point2(0.9, 0.1)])
>>> square.offset(0.1)
Polygon2([Point2(-0.1, -0.1), Point2(-0.1, 1.1), Point2(1.1, 1.1), Point2(1.1, -0.1)])
>>> square.offset(10) is None
True

Note

This is currently a naive implementation that does not properly handle non-local intersections that can split the polygon.

simplify(tolerance=0.0001)

Remove any duplicate points, within a certain tolerance:

>>> Polygon2([Point2(1, 1), Point2(2, 0), Point2(0, 0), Point2(1, 1)]).simplify()
Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])

Returns None if the resulting simplification would create a point:

>>> Polygon2([Point2(1, 1), Point2(2, 0), Point2(0, 0), Point2(1, 1)]).simplify(100) is None
True
to_clockwise()

Converts this polygon to a clockwise one if necessary:

>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]).to_clockwise()
Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
>>> Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]).to_clockwise()
Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
to_counterclockwise()

Converts this polygon to a clockwise one if necessary:

>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]).to_counterclockwise()
Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])
>>> Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]).to_counterclockwise()
Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])
petrify.shape.arc(center, radius, start, end, segments=10)[source]

Creates an arc of points around the given center with a specified radius and start and end angles, approximated with a fixed number of segments:

>>> points = arc(Point2(5, 0), 5, 0, tau / 2, segments = 3)
>>> [p.snap(0.1) for p in points]
[Point2(10.0, 0.0), Point2(5.0, 5.0), Point2(0.0, 0.0)]
petrify.shape.bezier(a, b, c, d, segments=10)[source]

Creates a bezier curve between the given control points, approximated with a given number of segments:

>>> points = bezier(Point2(0, 0), Point2(5, 0), Point2(5, 5), Point2(10, 5), 4)
>>> [p.snap(1.0) for p in points]
[Point2(0.0, 0.0), Point2(4.0, 1.0), Point2(6.0, 4.0), Point2(10.0, 5.0)]