275 lines
7.9 KiB
Lua
275 lines
7.9 KiB
Lua
-- Copyright (c) 2017 Laurent Zubiaur
|
|
--
|
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
-- of this software and associated documentation files (the "Software"), to deal
|
|
-- in the Software without restriction, including without limitation the rights
|
|
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
-- copies of the Software, and to permit persons to whom the Software is
|
|
-- furnished to do so, subject to the following conditions:
|
|
--
|
|
-- The above copyright notice and this permission notice shall be included in all
|
|
-- copies or substantial portions of the Software.
|
|
--
|
|
-- 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
-- SOFTWARE.
|
|
|
|
local ffi = require 'ffi'
|
|
-- NOTE ffi.load doesn't use package.cpath to search for libraries but rather the
|
|
-- default OS default search path (e.g. LD_LIBRARY_PATH).
|
|
|
|
-- Load Clipper from a shared library...
|
|
local C = ffi.load 'polyclipping'
|
|
-- or use the default namespace if Clipper's been compiled within the executable
|
|
-- local C = ffi.C
|
|
|
|
ffi.cdef[[
|
|
|
|
// Replace `int64_t` with `int32_t` if Clipper has been compiled with `use_int32`
|
|
typedef struct __cl_int_point { int64_t x, y; } cl_int_point;
|
|
typedef struct __cl_int_rect { int64_t left; int64_t top; int64_t right; int64_t bottom; } cl_int_rect;
|
|
// Replace `long long` with `int` if compiled with `use_int32`
|
|
typedef signed long long cInt;
|
|
|
|
typedef struct __cl_path cl_path;
|
|
typedef struct __cl_paths cl_paths;
|
|
typedef struct __cl_offset cl_offset;
|
|
typedef struct __cl_clipper cl_clipper;
|
|
|
|
const char* cl_err_msg();
|
|
|
|
// Path
|
|
cl_path* cl_path_new();
|
|
void cl_path_free(cl_path *self);
|
|
cl_int_point* cl_path_get(cl_path *self, int i);
|
|
bool cl_path_add(cl_path *self, cInt x, cInt y);
|
|
int cl_path_size(cl_path *self);
|
|
double cl_path_area(const cl_path *self);
|
|
bool cl_path_orientation(const cl_path *self);
|
|
void cl_path_reverse(cl_path *self);
|
|
int cl_path_point_in_polygon(cl_path *self,cInt x, cInt y);
|
|
cl_paths* cl_path_simplify(cl_path *self,int fillType);
|
|
cl_path* cl_path_clean_polygon(const cl_path *in, double distance);
|
|
|
|
// Paths
|
|
cl_paths* cl_paths_new();
|
|
void cl_paths_free(cl_paths *self);
|
|
cl_path* cl_paths_get(cl_paths *self, int i);
|
|
bool cl_paths_add(cl_paths *self, cl_path *path);
|
|
int cl_paths_size(cl_paths *self);
|
|
|
|
// ClipperOffset
|
|
cl_offset* cl_offset_new(double miterLimit,double roundPrecision);
|
|
void cl_offset_free(cl_offset *self);
|
|
cl_paths* cl_offset_path(cl_offset *self, cl_path *subj, double delta, int jointType, int endType);
|
|
cl_paths* cl_offset_paths(cl_offset *self, cl_paths *subj, double delta, int jointType, int endType);
|
|
void cl_offset_clear(cl_offset *self);
|
|
|
|
// Clipper
|
|
cl_clipper* cl_clipper_new();
|
|
void cl_clipper_free(cl_clipper *cl);
|
|
void cl_clipper_clear(cl_clipper *cl);
|
|
bool cl_clipper_add_path(cl_clipper *cl,cl_path *path, int pt, bool closed,const char *err);
|
|
bool cl_clipper_add_paths(cl_clipper *cl,cl_paths *paths, int pt, bool closed,const char *err);
|
|
void cl_clipper_reverse_solution(cl_clipper *cl, bool value);
|
|
void cl_clipper_preserve_collinear(cl_clipper *cl, bool value);
|
|
void cl_clipper_strictly_simple(cl_clipper *cl, bool value);
|
|
cl_paths* cl_clipper_execute(cl_clipper *cl,int clipType,int subjFillType,int clipFillType);
|
|
cl_int_rect cl_clipper_get_bounds(cl_clipper *cl);
|
|
]]
|
|
|
|
local ClipType = {
|
|
intersection = 0, union = 1, difference = 2, xor = 3
|
|
}
|
|
|
|
local JoinType = {
|
|
square = 0, round = 1, miter = 2
|
|
}
|
|
|
|
local EndType = {
|
|
closedPolygon = 0, closedLine = 1, openButt = 2, openSquare = 3, openRound = 4
|
|
}
|
|
|
|
-- Clipper constructor options
|
|
local InitOptions = {
|
|
reverseSolution = 1, strictlySimple = 2, preserveCollinear = 4
|
|
}
|
|
|
|
local PolyType = {
|
|
subject = 0, clip = 1
|
|
}
|
|
|
|
local PolyFillType = {
|
|
evenOdd = 1, nonZero = 2, positive = 2, negative = 3
|
|
}
|
|
|
|
local Path = {}
|
|
|
|
function Path.new()
|
|
return ffi.gc(C.cl_path_new(), C.cl_path_free)
|
|
end
|
|
|
|
function Path:add(x,y)
|
|
return C.cl_path_add(self,x,y)
|
|
end
|
|
|
|
function Path:get(i)
|
|
return C.cl_path_get(self,i-1)
|
|
end
|
|
|
|
function Path:size()
|
|
return C.cl_path_size(self)
|
|
end
|
|
|
|
function Path:area()
|
|
return C.cl_path_area(self)
|
|
end
|
|
|
|
function Path:reverse()
|
|
return C.cl_path_reverse(self)
|
|
end
|
|
|
|
function Path:orientation()
|
|
return C.cl_path_orientation(self)
|
|
end
|
|
|
|
function Path:contains(x,y)
|
|
return C.cl_path_point_in_polygon(self,x,y)
|
|
end
|
|
|
|
function Path:simplify(fillType)
|
|
fillType = fillType or 'evenOdd'
|
|
fillType = assert(PolyFillType[fillType],'unknown fill type')
|
|
return C.cl_path_simplify(self,fillType)
|
|
end
|
|
|
|
function Path:cleanPolygon(distance)
|
|
distance = distance or 1.415
|
|
return C.cl_path_clean_polygon(self,distance)
|
|
end
|
|
|
|
local Paths = {}
|
|
|
|
function Paths.new()
|
|
return ffi.gc(C.cl_paths_new(), C.cl_paths_free)
|
|
end
|
|
|
|
function Paths:add(path)
|
|
return C.cl_paths_add(self,path)
|
|
end
|
|
|
|
function Paths:get(i)
|
|
return C.cl_paths_get(self,i-1)
|
|
end
|
|
|
|
function Paths:size()
|
|
return C.cl_paths_size(self)
|
|
end
|
|
|
|
local ClipperOffset = {}
|
|
|
|
function ClipperOffset.new(miterLimit,roundPrecision)
|
|
local co = C.cl_offset_new(miterLimit or 2,roundPrecision or 0.25)
|
|
return ffi.gc(co, C.cl_offset_free)
|
|
end
|
|
|
|
function ClipperOffset:offsetPath(path,delta,jt,et)
|
|
jt,et = jt or 'square', et or 'openButt'
|
|
assert(JoinType[jt])
|
|
assert(EndType[et])
|
|
local out = C.cl_offset_path(self,path,delta,JoinType[jt],EndType[et])
|
|
if out == nil then
|
|
error(ffi.string(C.cl_err_msg()))
|
|
end
|
|
return out
|
|
end
|
|
|
|
function ClipperOffset:offsetPaths(paths,delta,jt,et)
|
|
jt,et = jt or 'square', et or 'openButt'
|
|
assert(JoinType[jt])
|
|
assert(EndType[et])
|
|
local out = C.cl_offset_paths(self,paths,delta,JoinType[jt],EndType[et])
|
|
if out == nil then
|
|
error(ffi.string(C.cl_err_msg()))
|
|
end
|
|
return out
|
|
end
|
|
|
|
function ClipperOffset:clear()
|
|
C.cl_offset_clear(self)
|
|
end
|
|
|
|
-- Clipper
|
|
|
|
local Clipper = {}
|
|
|
|
function Clipper.new(...)
|
|
local cl = C.cl_clipper_new()
|
|
for _,opt in ipairs {...} do
|
|
assert(InitOptions[opt])
|
|
if opt == 'strictlySimple' then
|
|
C.cl_clipper_strictly_simple(true)
|
|
elseif opt == 'reverseSolution' then
|
|
C.cl_clipper_reverse_solution(true)
|
|
else
|
|
C.cl_clipper_preserve_collinear(true)
|
|
end
|
|
end
|
|
return ffi.gc(cl, C.cl_clipper_free)
|
|
end
|
|
|
|
function Clipper:clear()
|
|
C.cl_clipper_clear(self)
|
|
end
|
|
|
|
function Clipper:addPath(path,pt,closed)
|
|
assert(path,'path is nil')
|
|
assert(PolyType[pt],'unknown polygon type')
|
|
if closed == nil then closed = true end
|
|
C.cl_clipper_add_path(self,path,PolyType[pt],closed,err);
|
|
end
|
|
|
|
function Clipper:addPaths(paths,pt,closed)
|
|
assert(paths,'paths is nil')
|
|
assert(PolyType[pt],'unknown polygon type')
|
|
if closed == nil then closed = true end
|
|
if not C.cl_clipper_add_paths(self,paths,PolyType[pt],closed,err) then
|
|
error(ffi.string(C.cl_err_msg()))
|
|
end
|
|
end
|
|
|
|
function Clipper:execute(clipType,subjFillType,clipFillType)
|
|
subjFillType = subjFillType or 'evenOdd'
|
|
clipFillType = clipFillType or 'evenOdd'
|
|
clipType = assert(ClipType[clipType],'unknown clip type')
|
|
subjFillType = assert(PolyFillType[subjFillType],'unknown fill type')
|
|
clipFillType = assert(PolyFillType[clipFillType],'unknown fill type')
|
|
local out = C.cl_clipper_execute(self,clipType,subjFillType,clipFillType)
|
|
-- XXX test `not nil` return false ?!
|
|
if out == nil then
|
|
error(ffi.string(C.cl_err_msg()))
|
|
end
|
|
return out
|
|
end
|
|
|
|
function Clipper:getBounds()
|
|
local r = C.cl_clipper_get_bounds(self)
|
|
return tonumber(r.left),tonumber(r.top),tonumber(r.right),tonumber(r.bottom)
|
|
end
|
|
|
|
ffi.metatype('cl_path', {__index = Path})
|
|
ffi.metatype('cl_paths', {__index = Paths})
|
|
ffi.metatype('cl_offset', {__index = ClipperOffset})
|
|
ffi.metatype('cl_clipper', {__index = Clipper})
|
|
|
|
return {
|
|
Path = Path.new,
|
|
Paths = Paths.new,
|
|
ClipperOffset = ClipperOffset.new,
|
|
Clipper = Clipper.new,
|
|
}
|