Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make miter limit and arc tolerance configurable for polypath offsetting #36369

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions core/bind/core_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1680,9 +1680,9 @@ Array _Geometry::intersect_polyline_with_polygon_2d(const Vector<Vector2> &p_pol
return ret;
}

Array _Geometry::offset_polygon_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type) {
Array _Geometry::offset_polygon_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyConnection p_con_type, real_t p_miter_limit, real_t p_arc_tolerance) {

Vector<Vector<Point2> > polys = Geometry::offset_polygon_2d(p_polygon, p_delta, Geometry::PolyJoinType(p_join_type));
Vector<Vector<Point2> > polys = Geometry::offset_polygon_2d(p_polygon, p_delta, Geometry::PolyConnection(p_con_type), p_miter_limit, p_arc_tolerance);

Array ret;

Expand All @@ -1692,9 +1692,9 @@ Array _Geometry::offset_polygon_2d(const Vector<Vector2> &p_polygon, real_t p_de
return ret;
}

Array _Geometry::offset_polyline_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
Array _Geometry::offset_polyline_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyConnection p_con_type, real_t p_miter_limit, real_t p_arc_tolerance) {

Vector<Vector<Point2> > polys = Geometry::offset_polyline_2d(p_polygon, p_delta, Geometry::PolyJoinType(p_join_type), Geometry::PolyEndType(p_end_type));
Vector<Vector<Point2> > polys = Geometry::offset_polyline_2d(p_polygon, p_delta, Geometry::PolyConnection(p_con_type), p_miter_limit, p_arc_tolerance);

Array ret;

Expand Down Expand Up @@ -1780,8 +1780,8 @@ void _Geometry::_bind_methods() {
ClassDB::bind_method(D_METHOD("clip_polyline_with_polygon_2d", "polyline", "polygon"), &_Geometry::clip_polyline_with_polygon_2d);
ClassDB::bind_method(D_METHOD("intersect_polyline_with_polygon_2d", "polyline", "polygon"), &_Geometry::intersect_polyline_with_polygon_2d);

ClassDB::bind_method(D_METHOD("offset_polygon_2d", "polygon", "delta", "join_type"), &_Geometry::offset_polygon_2d, DEFVAL(JOIN_SQUARE));
ClassDB::bind_method(D_METHOD("offset_polyline_2d", "polyline", "delta", "join_type", "end_type"), &_Geometry::offset_polyline_2d, DEFVAL(JOIN_SQUARE), DEFVAL(END_SQUARE));
ClassDB::bind_method(D_METHOD("offset_polygon_2d", "polygon", "delta", "connection_type", "miter_limit", "arc_tolerance"), &_Geometry::offset_polygon_2d, DEFVAL(JOIN_SQUARE), DEFVAL(2.0), DEFVAL(0.25));
ClassDB::bind_method(D_METHOD("offset_polyline_2d", "polyline", "delta", "connection_type", "miter_limit", "arc_tolerance"), &_Geometry::offset_polyline_2d, DEFVAL(JOIN_SQUARE | END_SQUARE), DEFVAL(2.0), DEFVAL(0.25));

ClassDB::bind_method(D_METHOD("make_atlas", "sizes"), &_Geometry::make_atlas);

Expand All @@ -1793,7 +1793,6 @@ void _Geometry::_bind_methods() {
BIND_ENUM_CONSTANT(JOIN_SQUARE);
BIND_ENUM_CONSTANT(JOIN_ROUND);
BIND_ENUM_CONSTANT(JOIN_MITER);

BIND_ENUM_CONSTANT(END_POLYGON);
BIND_ENUM_CONSTANT(END_JOINED);
BIND_ENUM_CONSTANT(END_BUTT);
Expand Down
27 changes: 12 additions & 15 deletions core/bind/core_bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -411,29 +411,26 @@ class _Geometry : public Object {
Array intersect_polyline_with_polygon_2d(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon); // Chop.

// 2D offset polygons/polylines.
enum PolyJoinType {
JOIN_SQUARE,
JOIN_ROUND,
JOIN_MITER
enum PolyConnection {
JOIN_SQUARE = 1 << 0,
JOIN_ROUND = 1 << 1,
JOIN_MITER = 1 << 2,
END_POLYGON = 1 << 3,
END_JOINED = 1 << 4,
END_BUTT = 1 << 5,
END_SQUARE = 1 << 6,
END_ROUND = 1 << 7
};
enum PolyEndType {
END_POLYGON,
END_JOINED,
END_BUTT,
END_SQUARE,
END_ROUND
};
Array offset_polygon_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type = JOIN_SQUARE);
Array offset_polyline_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type = JOIN_SQUARE, PolyEndType p_end_type = END_SQUARE);
Array offset_polygon_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyConnection p_con_type = JOIN_SQUARE, real_t p_miter_limit = 2.0, real_t p_arc_tolerance = 0.25);
Array offset_polyline_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyConnection p_con_type = PolyConnection(JOIN_SQUARE | END_SQUARE), real_t p_miter_limit = 2.0, real_t p_arc_tolerance = 0.25);

Dictionary make_atlas(const Vector<Size2> &p_rects);

_Geometry();
};

VARIANT_ENUM_CAST(_Geometry::PolyBooleanOperation);
VARIANT_ENUM_CAST(_Geometry::PolyJoinType);
VARIANT_ENUM_CAST(_Geometry::PolyEndType);
VARIANT_ENUM_CAST(_Geometry::PolyConnection);

class _File : public Reference {

Expand Down
31 changes: 19 additions & 12 deletions core/math/geometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1128,28 +1128,35 @@ Vector<Vector<Point2> > Geometry::_polypaths_do_operation(PolyBooleanOperation p
return polypaths;
}

Vector<Vector<Point2> > Geometry::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
Vector<Vector<Point2> > Geometry::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyConnection p_con_type, real_t p_miter_limit, real_t p_arc_tolerance) {

using namespace ClipperLib;

JoinType jt = jtSquare;

switch (p_join_type) {
case JOIN_SQUARE: jt = jtSquare; break;
case JOIN_ROUND: jt = jtRound; break;
case JOIN_MITER: jt = jtMiter; break;
if (p_con_type & JOIN_SQUARE) {
jt = jtSquare;
} else if (p_con_type & JOIN_ROUND) {
jt = jtRound;
} else if (p_con_type & JOIN_MITER) {
jt = jtMiter;
}

EndType et = etClosedPolygon;

switch (p_end_type) {
case END_POLYGON: et = etClosedPolygon; break;
case END_JOINED: et = etClosedLine; break;
case END_BUTT: et = etOpenButt; break;
case END_SQUARE: et = etOpenSquare; break;
case END_ROUND: et = etOpenRound; break;
if (p_con_type & END_POLYGON) {
et = etClosedPolygon;
} else if (p_con_type & END_JOINED) {
et = etClosedLine;
} else if (p_con_type & END_BUTT) {
et = etOpenButt;
} else if (p_con_type & END_SQUARE) {
et = etOpenSquare;
} else if (p_con_type & END_ROUND) {
et = etOpenRound;
}
ClipperOffset co(2.0, 0.25 * SCALE_FACTOR); // Defaults from ClipperOffset.

ClipperOffset co(p_miter_limit, p_arc_tolerance * SCALE_FACTOR);
Path path;

// Need to scale points (Clipper's requirement for robust computation).
Expand Down
32 changes: 15 additions & 17 deletions core/math/geometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -777,17 +777,15 @@ class Geometry {
OPERATION_INTERSECTION,
OPERATION_XOR
};
enum PolyJoinType {
JOIN_SQUARE,
JOIN_ROUND,
JOIN_MITER
};
enum PolyEndType {
END_POLYGON,
END_JOINED,
END_BUTT,
END_SQUARE,
END_ROUND
enum PolyConnection {
JOIN_SQUARE = 1 << 0,
JOIN_ROUND = 1 << 1,
JOIN_MITER = 1 << 2,
END_POLYGON = 1 << 3,
END_JOINED = 1 << 4,
END_BUTT = 1 << 5,
END_SQUARE = 1 << 6,
END_ROUND = 1 << 7
};

static Vector<Vector<Point2> > merge_polygons_2d(const Vector<Point2> &p_polygon_a, const Vector<Point2> &p_polygon_b) {
Expand Down Expand Up @@ -820,16 +818,16 @@ class Geometry {
return _polypaths_do_operation(OPERATION_INTERSECTION, p_polyline, p_polygon, true);
}

static Vector<Vector<Point2> > offset_polygon_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type) {
static Vector<Vector<Point2> > offset_polygon_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyConnection p_con_type, real_t p_miter_limit, real_t p_arc_tolerance) {

return _polypath_offset(p_polygon, p_delta, p_join_type, END_POLYGON);
return _polypath_offset(p_polygon, p_delta, PolyConnection(p_con_type | END_POLYGON), p_miter_limit, p_arc_tolerance);
}

static Vector<Vector<Point2> > offset_polyline_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
static Vector<Vector<Point2> > offset_polyline_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyConnection p_con_type, real_t p_miter_limit, real_t p_arc_tolerance) {

ERR_FAIL_COND_V_MSG(p_end_type == END_POLYGON, Vector<Vector<Point2> >(), "Attempt to offset a polyline like a polygon (use offset_polygon_2d instead).");
ERR_FAIL_COND_V_MSG(p_con_type & END_POLYGON, Vector<Vector<Point2> >(), "Attempt to offset a polyline like a polygon (use offset_polygon_2d instead).");

return _polypath_offset(p_polygon, p_delta, p_join_type, p_end_type);
return _polypath_offset(p_polygon, p_delta, p_con_type, p_miter_limit, p_arc_tolerance);
}

static Vector<int> triangulate_delaunay_2d(const Vector<Vector2> &p_points) {
Expand Down Expand Up @@ -1016,7 +1014,7 @@ class Geometry {

private:
static Vector<Vector<Point2> > _polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open = false);
static Vector<Vector<Point2> > _polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type);
static Vector<Vector<Point2> > _polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyConnection p_con_type, real_t p_miter_limit = 2.0, real_t p_arc_tolerance = 0.25); // Defaults from ClipperOffset.
};

#endif
48 changes: 31 additions & 17 deletions doc/classes/Geometry.xml
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,17 @@
</argument>
<argument index="1" name="delta" type="float">
</argument>
<argument index="2" name="join_type" type="int" enum="Geometry.PolyJoinType" default="0">
<argument index="2" name="connection_type" type="int" enum="Geometry.PolyConnection" default="1">
</argument>
<argument index="3" name="miter_limit" type="float" default="2.0">
</argument>
<argument index="4" name="arc_tolerance" type="float" default="0.25">
</argument>
<description>
Inflates or deflates [code]polygon[/code] by [code]delta[/code] units (pixels). If [code]delta[/code] is positive, makes the polygon grow outward. If [code]delta[/code] is negative, shrinks the polygon inward. Returns an array of polygons because inflating/deflating may result in multiple discrete polygons. Returns an empty array if [code]delta[/code] is negative and the absolute value of it approximately exceeds the minimum bounding rectangle dimensions of the polygon.
Each polygon's vertices will be rounded as determined by [code]join_type[/code], see [enum PolyJoinType].
Each polygon's vertices will be rounded as determined by [code]connection_type[/code] from one of the [code]JOIN_*[/code] types, see [enum PolyConnection].
[code]miter_limit[/code] sets the maximum distance in multiples of [code]delta[/code] that vertices can be offset from their original positions before squaring is applied. The default value is 2.0 (twice delta) which is the smallest value that's allowed to avoid generating unacceptably long 'spikes' at very acute angles.
[code]arc_tolerance[/code] sets the maximum distance the flattened path will deviate from the mathematical representation of an arc. Smaller values will increase smoothness at a cost of performance. Only relevant when [code]connection_type[/code] is set to [code]JOIN_ROUND[/code].
The operation may result in an outer polygon (boundary) and inner polygon (hole) produced which could be distinguished by calling [method is_polygon_clockwise].
</description>
</method>
Expand All @@ -311,14 +317,22 @@
</argument>
<argument index="1" name="delta" type="float">
</argument>
<argument index="2" name="join_type" type="int" enum="Geometry.PolyJoinType" default="0">
<argument index="2" name="connection_type" type="int" enum="Geometry.PolyConnection" default="65">
</argument>
<argument index="3" name="miter_limit" type="float" default="2.0">
</argument>
<argument index="3" name="end_type" type="int" enum="Geometry.PolyEndType" default="3">
<argument index="4" name="arc_tolerance" type="float" default="0.25">
</argument>
<description>
Inflates or deflates [code]polyline[/code] by [code]delta[/code] units (pixels), producing polygons. If [code]delta[/code] is positive, makes the polyline grow outward. Returns an array of polygons because inflating/deflating may result in multiple discrete polygons. If [code]delta[/code] is negative, returns an empty array.
Each polygon's vertices will be rounded as determined by [code]join_type[/code], see [enum PolyJoinType].
Each polygon's endpoints will be rounded as determined by [code]end_type[/code], see [enum PolyEndType].
Each polygon's intermediate vertices and endpoints will be rounded as determined by [code]connection_type[/code] from a combination of [code]JOIN_*[/code] and [code]END_*[/code] types respectively, see [enum PolyConnection]. For instance:
[codeblock]
# Produces a thin corridor with round corner passages and dead-ends.
var polyline = [Vector2(0, 0), Vector2(100, 0), Vector2(100, 100), Vector2(0, 100)]
var polygons = Geometry.offset_polyline_2d(polyline, 20.0, Geometry.JOIN_ROUND | Geometry.END_BUTT)
[/codeblock]
[code]miter_limit[/code] sets the maximum distance in multiples of [code]delta[/code] that vertices can be offset from their original positions before squaring is applied. The default value is 2.0 (twice delta) which is the smallest value that's allowed to avoid generating unacceptably long 'spikes' at very acute angles.
[code]arc_tolerance[/code] sets the maximum distance the flattened path will deviate from the mathematical representation of an arc. Smaller values will increase smoothness at a cost of performance. Only relevant when [code]connection_type[/code] is set to [code]JOIN_ROUND[/code].
The operation may result in an outer polygon (boundary) and inner polygon (hole) produced which could be distinguished by calling [method is_polygon_clockwise].
</description>
</method>
Expand Down Expand Up @@ -476,28 +490,28 @@
<constant name="OPERATION_XOR" value="3" enum="PolyBooleanOperation">
Create regions where either subject or clip polygons are filled but not where both are filled.
</constant>
<constant name="JOIN_SQUARE" value="0" enum="PolyJoinType">
Squaring is applied uniformally at all convex edge joins at [code]1 * delta[/code].
<constant name="JOIN_SQUARE" value="1" enum="PolyConnection">
Squaring is applied uniformly at all convex edge joins at [code]1 * delta[/code].
</constant>
<constant name="JOIN_ROUND" value="1" enum="PolyJoinType">
<constant name="JOIN_ROUND" value="2" enum="PolyConnection">
While flattened paths can never perfectly trace an arc, they are approximated by a series of arc chords.
</constant>
<constant name="JOIN_MITER" value="2" enum="PolyJoinType">
<constant name="JOIN_MITER" value="4" enum="PolyConnection">
There's a necessary limit to mitered joins since offsetting edges that join at very acute angles will produce excessively long and narrow "spikes". For any given edge join, when miter offsetting would exceed that maximum distance, "square" joining is applied.
</constant>
<constant name="END_POLYGON" value="0" enum="PolyEndType">
Endpoints are joined using the [enum PolyJoinType] value and the path filled as a polygon.
<constant name="END_POLYGON" value="8" enum="PolyConnection">
Endpoints are joined using one of the [code]JOIN_*[/code] values and the path filled as a polygon.
</constant>
<constant name="END_JOINED" value="1" enum="PolyEndType">
Endpoints are joined using the [enum PolyJoinType] value and the path filled as a polyline.
<constant name="END_JOINED" value="16" enum="PolyConnection">
Endpoints are joined using one of the [code]JOIN_*[/code] values and the path filled as a polyline.
</constant>
<constant name="END_BUTT" value="2" enum="PolyEndType">
<constant name="END_BUTT" value="32" enum="PolyConnection">
Endpoints are squared off with no extension.
</constant>
<constant name="END_SQUARE" value="3" enum="PolyEndType">
<constant name="END_SQUARE" value="64" enum="PolyConnection">
Endpoints are squared off and extended by [code]delta[/code] units.
</constant>
<constant name="END_ROUND" value="4" enum="PolyEndType">
<constant name="END_ROUND" value="128" enum="PolyConnection">
Endpoints are rounded off and extended by [code]delta[/code] units.
</constant>
</constants>
Expand Down