|
| 1 | +# Clipper.jl |
| 2 | + |
| 3 | +Clipper.jl is a Julia wrapper for [Angus Johnson's Clipper library (ver. 6.4.2)](http://www.angusj.com/delphi/clipper.php). |
| 4 | + |
| 5 | +It can be used for the following two tasks: |
| 6 | +- offsetting a polygon |
| 7 | +- compute boolean operations between two or more polygons |
| 8 | + |
| 9 | +## General description of a polygon |
| 10 | + |
| 11 | +A polygon consists of a list of vertices $v_1, \dots, v_n$ such that $v_1$ is connected to $v_2$ and $v_n$. $v_i$ is connected to $v_{i-1}$ and $v_{i+1}$. |
| 12 | +All functionality in Clipper only works with vertices that have discrete coordinates. Therefore the struct [`IntPoint`](@ref) is used. |
| 13 | +Points which consist of two floating point coordinates can however be easily converted using the [`IntPoint`](@ref) method. |
| 14 | + |
| 15 | +```julia |
| 16 | +p = (1.1, 2.1) |
| 17 | +ip = IntPoint(p..., 1, 3) # [110, 210] |
| 18 | +``` |
| 19 | + |
| 20 | +Here `1` is the magnitude and `3` is the number of significant digits. |
| 21 | + |
| 22 | +There is also the function [`tofloat`](@ref) which can be used to convert a [`IntPoint`](@ref) back to the floating point coordinates. |
| 23 | + |
| 24 | +```julia |
| 25 | +tofloat(ip, 1, 3) # (1.1, 2.1) |
| 26 | +``` |
| 27 | + |
| 28 | +Using broadcasting you can also easily convert several points like a whole polygon this way: |
| 29 | + |
| 30 | +```julia |
| 31 | +ps = rand(100, 2) |
| 32 | +ips = IntPoint.(ps[:,1], ps[:,2], 1, 3) |
| 33 | +``` |
| 34 | + |
| 35 | +and back |
| 36 | +```julia |
| 37 | +tofloat.(ips, 1, 3) |
| 38 | +``` |
| 39 | + |
| 40 | +## Offsetting a polygon |
| 41 | + |
| 42 | +[Original Clipper documentation](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/_Body.htm). |
| 43 | + |
| 44 | +A polygon can be offset (inflated/deflated) in the following way using Clipper.jl: |
| 45 | + |
| 46 | + |
| 47 | +```julia |
| 48 | +polygon = IntPoint[] |
| 49 | +push!(polygon, IntPoint(348,257)) |
| 50 | +push!(polygon, IntPoint(364,148)) |
| 51 | +push!(polygon, IntPoint(362,148)) |
| 52 | +push!(polygon, IntPoint(326,241)) |
| 53 | +push!(polygon, IntPoint(295,219)) |
| 54 | +push!(polygon, IntPoint(258,88)) |
| 55 | +push!(polygon, IntPoint(440,129)) |
| 56 | +push!(polygon, IntPoint(370,196)) |
| 57 | +push!(polygon, IntPoint(372,275)) |
| 58 | + |
| 59 | +co = ClipperOffset() |
| 60 | +add_path!(co, polygon, JoinTypeRound, EndTypeClosedPolygon) |
| 61 | +offset_polygons = execute(co, -7.0) |
| 62 | +``` |
| 63 | + |
| 64 | +```@meta |
| 65 | +# using Luxor |
| 66 | +# fpolygon = Point.(tofloat.(polygon, 3,3)) |
| 67 | +# foffset_polygons = [Point.(tofloat.(offset_polygon, 3,3)) for offset_polygon in offset_polygons] |
| 68 | +# @png begin |
| 69 | +# translate(-650, -400) |
| 70 | +# scale(2) |
| 71 | +# sethue("blue") |
| 72 | +# poly(fpolygon, :stroke, close=true) |
| 73 | +# sethue("green") |
| 74 | +# setopacity(0.4) |
| 75 | +# poly.(foffset_polygons, :fill, close=true) |
| 76 | +# end 600 600 "offset_in.png" |
| 77 | +``` |
| 78 | + |
| 79 | + |
| 80 | +In the image above the blue polygon is the initial polygon and by offsetting it two polygons are created |
| 81 | +which are drawn as the filled green polygons. |
| 82 | + |
| 83 | +We can also inflate the polygon by using a positive value in the execute function. |
| 84 | +```julia |
| 85 | +offset_polygons = execute(co, 7.0) |
| 86 | +``` |
| 87 | + |
| 88 | + |
| 89 | + |
| 90 | +In this example we can visualize the meaning of the third argument in the `add_path!` function which is currently set to `JoinTypeRound`. |
| 91 | + |
| 92 | +```julia |
| 93 | +co = ClipperOffset() |
| 94 | +add_path!(co, polygon, JoinTypeRound, EndTypeClosedPolygon) |
| 95 | +round_offset_polygons = execute(co, 7.0) |
| 96 | + |
| 97 | +co = ClipperOffset() |
| 98 | +add_path!(co, polygon, JoinTypeSquare, EndTypeClosedPolygon) |
| 99 | +square_offset_polygons = execute(co, 7.0) |
| 100 | + |
| 101 | +co = ClipperOffset() |
| 102 | +add_path!(co, polygon, JoinTypeMiter, EndTypeClosedPolygon) |
| 103 | +miter_offset_polygons = execute(co, 7.0) |
| 104 | +``` |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +`JoinTypeRound` produces rounded corners, `JoinTypeSquare` produces squared corners. |
| 109 | +In the case of `JoinTypeMiter` it depends on the degree of the angle or in different words the maximum offsetted distance if corners. |
| 110 | +would not be squared. If the maximum distance is bigger than `MiterLimit * delta` than it is squared which would be the case in the upper right corner. |
| 111 | + |
| 112 | +The `MiterLimit` is the first argument of `ClipperOffset` for example `ClipperOffset(3.0)` would set it to `3.0` (default is `2.0`). |
| 113 | +`delta` is simply the distance we want to offset in our case above we set it to `7.0` as the second parameter of the `execute` function. |
| 114 | + |
| 115 | +The three different types are also explained in the [official documentation](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm). |
| 116 | + |
| 117 | +## Boolean operations between two or more polygons |
| 118 | + |
| 119 | +Needs to be written. Feel free to open a PR. |
| 120 | + |
| 121 | + |
0 commit comments