Math::Clipper(3pm)
π Math::Clipper β the perl man page, bestie edition π
okay so this is Math::Clipper(3pm) from the User Contributed Perl Documentation section, and it's about to be your new obsession. polygon clipping?? in 2D?? yes queen, let's get into it π¦
β¨ NAME β¨
Math::Clipper β Polygon clipping in 2D π
π SYNOPSIS π
bestie here's the whole vibe in code, look:
use Math::Clipper ':all';
my $clipper = Math::Clipper->new;
$clipper->add_subject_polygon( [ [-100, 100], [ 0, -200], [100, 100] ] );
$clipper->add_clip_polygon( [ [-100, -100], [100, -100], [ 0, 200] ] );
my $result = $clipper->execute(CT_DIFFERENCE);
# $result is now a reference to an array of three triangles
$clipper->clear();
# all data from previous operation cleared
# object ready for reuse
# Example with floating point coordinates:
# Clipper requires integer input.
# These polygons won't work.
my $poly_1 = [
[-0.001, 0.001],
[0, -0.002],
[0.001, 0.001]
];
my $poly_2 = [
[-0.001, -0.001],
[0.001, -0.001],
[0, 0.002]
];
# But we can have them automatically scaled up (in place) to a safe integer range
my $scale = integerize_coordinate_sets( $poly_1 , $poly_2 );
$clipper->add_subject_polygon( $poly_1 );
$clipper->add_clip_polygon( $poly_2 );
my $result = $clipper->execute(CT_DIFFERENCE);
# to convert the results (in place) back to the original scale:
unscale_coordinate_sets( $scale, $result );
# Example using 32 bit integer math instead of the default 53 or 64
# (less precision, a bit faster)
my $clipper32 = Math::Clipper->new;
my $scale32 = integerize_coordinate_sets( { bits=>32 } , $poly_1 , $poly_2 );
$clipper32->add_subject_polygon( $poly_1 );
$clipper32->add_clip_polygon( $poly_2 );
my $result32 = $clipper->execute(CT_DIFFERENCE);
unscale_coordinate_sets( $scale32, $result32 );
like. look at it go. πβοΈπ¨
π DESCRIPTION π
okay so plot twist: Clipper is actually a C++ (and Delphi) library β yes, a whole other language jumped out β and what it does is implement polygon clipping. this perl module is our little window into it. π
πΈ Exports πΈ
the module optionally dumps a few constants straight into your namespace. standard Exporter semantics apply, which includes the ":all" tag. classic. π
the exportable constants list is made up of the clip operation types β and honestly they're pretty self-explanatory:
CT_INTERSECTION
CT_UNION
CT_DIFFERENCE
CT_XOR
plus!! there are also constants that tell you how the polygon gets filled during the clipping operation:
PFT_EVENODD
PFT_NONZERO
PFT_POSITIVE
PFT_NEGATIVE
so many options, so much power, we love to see it π«Ά
π¦ CONVENTIONS π¦
INTEGERS: Clipper 4.x works with polygons that have integer coordinates. that's the whole thing. so if your data is in floating point format, you're gonna have to scale it appropriately into the available integer range before those polygons get added to a clipper object. (good news β scaling utilities are provided right here for you π)
a Polygon is represented by a reference to an array of 2D points. and a Point, in turn? a reference to an array holding two numbers: the X and Y coordinates. here's a 1x1 square polygon as an example:
[ [0, 0],
[1, 0],
[1, 1],
[0, 1] ]
sets of polygons β like the ones the execute method hands back to you β are represented by an array reference containing 0 or more polygons. no cap.
Clipper also has a polygon type that explicitly pairs an outer polygon with any additional polygons that describe "holes" in the filled region of that outer polygon. this one's called an ExPolygon. the data structure for an ExPolygon goes like this,:
{ outer => [ <polygon> ],
holes => [
[ <polygon> ],
[ <polygon> ],
...
]
}
Clipper additionally offers an export type named PolyTree, which represents several nested polygons by assigning each one to its parent. the PolyTree structure is an arrayref that looks like this one:
[
{ outer => [ ..points.. ], children => [] },
{
outer => [ ..points.. ],
children => [
{ hole => [ ..points.. ], children => [] },
{ hole => [ ..points.. ], children => [] },
],
}
]
each item is a hashref which may contain either the contour or the hole key, holding the polygon points. it also contains a children key containing an arrayref of hashrefs itself, and so on, forever, like a little family tree π³. but heads up bestie β the Clipper documentation reports that it's more computationally expensive to process (roughly 5-10% slower), so it should only be used when parent-child polygon relationships are actually needed, and not just polygon coordinates. don't do it for the aesthetic. π
the "fill type" of a polygon refers to the strategy used to figure out which side of a polygon is the inside, and whether a polygon represents a filled region or a hole. you may optionally specify the fill type of your subject and clip polygons when you call the execute method. β¨
when you specify the NONZERO fill type, the winding order of polygon points is what determines whether a polygon is filled, or represents a hole. Clipper uses the convention that counter clockwise wound polygons are filled, while clockwise wound polygons represent holes. this strategy is more explicit, but β and this is the tradeoff, bestie β it requires that you manage the winding order of all polygons. it's giving responsibility. π
the EVENODD fill type strategy uses a test segment, with its start point inside a polygon, and its end point out beyond the bounding box of all polygons in question. all intersections between that segment and all polygons get calculated. if the intersection count is odd, the inner-most (if nested) polygon containing the segment's start point is considered to be filled. when the intersection count is even, that polygon is considered to be a hole. math girlies stay winning π
for an example case in which NONZERO and EVENODD produce different results, see the "NONZERO vs. EVENODD" section below. (it's coming, keep reading π)
π METHODS π
π new
constructor that takes no arguments and returns a new Math::Clipper object. that's it, that's the whole method. simple. clean. iconic. β¨
π add_subject_polygon
adds a(nother) polygon to the set of polygons that will be clipped. π
π§ add_clip_polygon
adds a(nother) polygon to the set of polygons that define the clipping operation. π
β add_subject_polygons
works exactly the same as "add_subject_polygon" but adds a whole set of polygons. efficiency queen.
πΈ add_clip_polygons
works exactly the same as "add_clip_polygon" but adds a whole set of polygons. you see the pattern π
π₯ execute
this is the main character. it performs the actual clipping operation, and returns the result as a reference to an array of polygons.
my $result = $clipper->execute( CT_UNION );
Parameters: the type of the clipping operation, defined by one of the constants ("CT_*").
additionally!! you may define the polygon fill types ("PFT_*") of the subject and clipping polygons as the second and third parameters respectively. by default, even-odd filling ("PFT_EVENODD") will be used. π
my $result = $clipper->execute( CT_UNION, PFT_EVENODD, PFT_EVENODD );
π ex_execute
like "execute", it performs the actual clipping operation β but it returns a reference to an array of ExPolygons. (see "CONVENTIONS" above, bestie π)
π pt_execute
like "execute", it performs the actual clipping operation β but it returns a PolyTree structure. (see "CONVENTIONS" above π¦)
π clear
so you wanna reuse a Math::Clipper object? valid. you can call the "clear" method to remove all polygons and internal data from previous clipping operations. fresh start, clean slate, new era. periodt. β¨
bestie this is only part 1!! there's more coming, don't go anywhere πΈπ
π§° UTILITY FUNCTIONS π§°
These are the little helper besties that make your life easier, no cap π
πͺ integerize_coordinate_sets
Takes an array of polygons and scales every single point coordinate so the values will fit in whatever integer range you've got available. It returns an array reference stuffed with the scaling factors it used for each coordinate column. Heads up: the polygon data gets scaled in-place. And the reason it hands you the scaling vector back is so you can "unscale" everything when you're done, using unscale_coordinate_sets. π«Ά
my $scale_vector = integerize_coordinate_sets( $poly1 , $poly2 , $poly3 );
The main point of this function is converting floating point coordinate data into integers. As of Clipper version 4, only integer coordinate data is allowed, periodt. This is what keeps the intersection algorithm robust, but ngl it's a bit of a vibe killer if your data is floating point.
This utility is literally designed to make converting your data to Clipper-friendly integers as easy as possible, while keeping as much precision as you can. When your clipping ops are done, hit unscale_coordinate_sets to scale the results back to your original scale. π
Convert all your polygons at once, in one single call to integerize_coordinate_sets, before you load them into your clipper object. The scaling factors have to be calculated so that all polygons involved fit into the available integer space.
By default the scaling is uniform across coordinate columns (like, the X values get scaled by the same factor as the Y values) which means all the scaling factors you get back are identical. In other words: by default, the aspect ratio between X and Y is constrained. π
You can pass options in an anonymous hash as the first argument to override the defaults. If the first argument is not a hash reference, it's taken as the first polygon to be scaled instead.
my $scale_vector = integerize_coordinate_sets( {
constrain => 0, # don't do uniform scaling
bits => 32 # use the +/-1,073,741,822 integer range
},
$poly1 , $poly2 , $poly3
);
The bits option can be 32, 53, or 64. The default is 53 or 64, depending on whether your Perl uses 64 bit integers AND long doubles by default. (The scaling involves math with native doubles, so just having 64 bit integers is not enough, bestie.)
Setting bits to 32 may give you a modest speed boost, because it lets Clipper skip calculations with big integer types. β‘
The constrain option is a boolean. Default is true. When you set it to false, each column of coordinates (X, Y) gets scaled independently. This can be useful when the domain of your X values is very much larger or smaller than the domain of your Y values, so you get better resolution for the smaller domain. The different scaling factors will be in the returned scaling vector (array reference).
This utility also works on coordinates with three or more dimensions π. Even though the context here is 2D, be aware of that if you happen to feed it 3D data. Large domains in those higher dimensions could squeeze the 2D data down to nothing if scaling is uniform.
πͺ unscale_coordinate_sets
This undoes the scaling that integerize_coordinate_sets did. Use it on the polygons returned by the execute method. Pass the scaling vector returned by integerize_coordinate_sets, plus the polygons you want to unscale. The polygon coordinates get updated in place. π
unscale_coordinate_sets($scale,$clipper_result);
πͺ offset
my $offset_polygons = offset($polygons, $distance);
my $offset_polygons = offset($polygons, $distance, $scale, $jointype, $miterlimit);
Takes a reference to an array of polygons ($polygons), a positive or negative offset dimension ($distance), and optionally a scaling factor ($scale), a join type ($jointype), and a numeric angle limit for the JT_MITER join type.
The polygons use the NONZERO fill strategy, so filled areas and holes can be specified by polygon winding order. π
A positive offset dimension makes filled polygons grow outward and their holes shrink. A negative offset makes polygons shrink and their holes grow.
Coordinates get multiplied by the scaling factor before the offset operation, and the results get divided by the scaling factor. The default scaling factor is 100. Setting the scaling factor higher gets you more points and smoother contours in the offset results.
Returns a fresh new set of polygons, offset by the given dimension. β¨
my $offset_polygons = offset($polygons, 5.5); # offset by 5.5
or
my $offset_polygons = offset($polygons, 5.5, 1000); # smoother results, proliferation of points
π¨ WARNING: as you increase the scaling factor, the number of points grows quickly and will happily consume all of your RAM. Large offset dimensions also contribute to a proliferation of points. Be careful, bestie π
Floating point data in the input is acceptable β in that case the scaling factor also determines how many decimal digits you'll get in the results. It is not necessary, and generally not desirable, to use integerize_coordinate_sets to prepare data for this function.
When you're doing negative offsets, you might find the winding order of the results is the opposite of what you expect, although this seems to be fixed in recent Clipper versions. Check the order and change it if it matters in your application. π
Join type can be one of JT_MITER, JT_ROUND or JT_SQUARE.
πͺ int_offset
my $offset_polygons = int_offset($polygons, $distance, $scale, $jointype, $miterlimit);
This one is a faster replacement for offset() when your input coordinates are integers. If you feed it floats, their decimal digits get truncated, so the offset might operate on invalid geometry (truncation can lead to self-intersecting polygons). Only use this one if your input polygons have integer coordinates only, fr fr. β οΈ
πͺ int_offset2
my $offset_polygons = int_offset($polygons, $distance1, $distance2, $scale, $jointype, $miterlimit);
Works like int_offset() but it does two consecutive offsets with the given distances. The whole reason the *offset2 functions exist is to dodge overhead when you need two consecutive offsets (scaling/unscaling only happens once, and no conversion to Perl variables happens in between). Efficient, iconic. π
πͺ ex_int_offset
my $offset_expolygons = ex_int_offset($polygons, $distance, $scale, $jointype, $miterlimit);
Works like int_offset() but it does a UNION operation on the resulting polygons and returns an arrayref of ExPolygons. π
πͺ ex_int_offset2
my $offset_expolygons = ex_int_offset2($polygons, $distance1, $distance2, $scale, $jointype, $miterlimit);
Works like ex_int_offset() but it does two consecutive offsets with the given distances before performing the UNION operation.
πͺ area
Returns the signed area of a single polygon. A counter clockwise wound polygon area will be positive. A clockwise wound polygon area will be negative. Coordinate data should be integers.
$area = area($polygon);
πͺ orientation
Determines the winding order of a polygon. It returns a true value if the polygon is counter-clockwise and you're assuming a display where the Y-axis coordinates are positive upward, or if the polygon is clockwise and you're assuming a positive-downward Y-axis. Coordinate data should be integers. The majority of 2D graphic display libraries put their origin (0,0) at the top left corner, so Y increases downward; however some libraries (Quartz, OpenGL) as well as non-display applications (CNC) assume Y increases upward. πΈ
my $poly = [ [0, 0] , [2, 0] , [1, 1] ]; # a counter clockwise wound polygon (assuming Y upward)
my $direction = orientation($poly);
# now $direction == 1
This function used to be named is_counter_clockwise(). That symbol is still exported for backwards compatibility; however you're encouraged to switch to orientation(), since the underlying Clipper library switched to it too to clarify the Y axis convention issue. π
πͺ simplify_polygon =head2 simplify_polygons
These functions convert self-intersecting polygons (aka complex polygons) into simple polygons. simplify_polygon() takes a single polygon as its first argument, while simplify_polygons() takes multiple polygons in a single arrayref. The second argument must be a polyfilltype constant (PFT_*, see above). Both return an arrayref of polygons. β¨
π’ MAXIMUM COORDINATE VALUES AND 64 BIT SUPPORT π’
Clipper accepts 64 bit integer input, but it limits the domain of input coordinate values to +/-4,611,686,018,427,387,902, to leave enough overhead for certain calculations. Coordinate values up to these limits are possible with Perls built to support 64 bit integers. π
A typical Perl that supports 32 bit integers can alternatively store 53 bit integers as floating point numbers. In that case, the coordinate domain is limited to +/-9,007,199,254,740,992.
When optionally constraining coordinate values to 32 bit integers, the domain is +/-1,073,741,822.
The integerize_coordinate_sets utility function automatically respects whichever limit applies to your Perl build. π«Ά
β¨ NONZERO vs. EVENODD β¨
okay bestie this is THE part, the one everyone gets confused about, so let's go π«Ά
peep this example:
my $p1 = [ [0,0], [200000,0], [200000,200000] ]; # CCW
my $p2 = [ [0,200000], [0,0], [200000,200000] ]; # CCW
my $p3 = [ [0,0], [200000,0], [200000,200000], [0,200000] ]; # CCW
my $clipper = Math::Clipper->new;
$clipper->add_subject_polygon($p1);
$clipper->add_clip_polygons([$p2, $p3]);
my $result = $clipper->execute(CT_UNION, PFT_EVENODD, PFT_EVENODD);
so $p3 is a whole square, and $p1 and $p2 are two triangles that each cover one half of the $p3 area. The CT_UNION operation gives you different results depending on whether you pass PFT_EVENODD or PFT_NONZERO. these are literally the two different strategies Clipper uses to figure out filled vs. empty regions. two whole strategies. iconic.
let's break it down in detail ok: $p2 and $p3 are the clip polygons, and $p2 overlaps half of $p3. with the PFT_EVENODD fill strategy, the number of polygons overlapping in a given area is what decides whether that area is a hole or a filled region. odd number of polygons overlapping there? filled region π. even number? hole / empty region π³οΈ. so with PFT_EVENODD, winding order literally does not matter β the only thing that matters is where areas overlap.
so using PFT_EVENODD, and treating $p2 and $p3 as the set of clipping polygons: the fact that $p2 overlaps half of $p3 means the region where they overlap is empty. so effectively, in this example, the set of clipping polygons ends up defining the exact same shape as the subject polygon $p1. so the union is just the union of two identical polygons, and the result is a triangle equivalent to $p1. slay.
if instead you specify the PFT_NONZERO strategy, the set of clipping polygons gets read as two filled polygons, because of the winding order. the area where they overlap counts as filled, because there's at least one filled polygon in that area. so the set of clipping polygons in this case is equivalent to the square $p3, and the result of the CT_UNION operation is also equivalent to the square $p3. π
this is a great lil example of how PFT_NONZERO is more explicit, and maybe more intuitive. no cap.
π SEE ALSO
the SourceForge project page of Clipper, bestie:
<http://sourceforge.net/projects/polyclipping/>
π« VERSION
this module was built around, and includes, Clipper version 5.1.5.
π AUTHOR
the Perl module was written by:
Steffen Mueller (<smueller@cpan.org>), Mike Sheldrake and Alessandro Ranellucci (aar/alexrj)
but the underlying library Clipper was written by Angus Johnson. check the SourceForge project page for contact info. π
π COPYRIGHT AND LICENSE
the Math::Clipper module is
Copyright (C) 2010, 2011, 2014 by Steffen Mueller
Copyright (C) 2011, 2018, 2019 by Mike Sheldrake
Copyright (C) 2012, 2013 by Alessandro Ranellucci and Mike Sheldrake
but we are shipping a copy of the Clipper C++ library, which is
Copyright (C) 2010, 2011, 2012 by Angus Johnson.
Math::Clipper is available under the same license as Clipper itself. this is the boost license:
Boost Software License - Version 1.0 - August 17th, 2003
http://www.boost.org/LICENSE_1_0.txt
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
and that's a wrap on the whole page, bestie π¦ tysm for reading all three parts, you ate. this one's from perl v5.42.3 and dated 2026-08-19, filed under Math::Clipper(3pm) β go forth and clip with main character energy π
β¨πΈ
π¦ where this page comes from
- ubuntu stonking: libmath-clipper-perl 1.29-2build6 Β· BSL-1.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms