From 5d6d68e2aa3173574acbc7e00470eb418770cc46 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 22 Jun 2021 17:16:01 -0400 Subject: [PATCH] stub in wasm poly diff --- src/geo/polygons.js | 9 +++++- src/geo/wasm.js | 25 ++++++++++++++++ wasm/kiri-geo.cpp | 71 +++++++++++++++++++++++++++++++++++++++++--- wasm/kiri-geo.wasm | Bin 64993 -> 65585 bytes 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/src/geo/polygons.js b/src/geo/polygons.js index 64e954f4..6d6a1bf0 100644 --- a/src/geo/polygons.js +++ b/src/geo/polygons.js @@ -299,13 +299,14 @@ min = minArea || 0.1, out = []; - function filter(from, to) { + function filter(from, to = []) { from.forEach(function(poly) { if (poly.area() >= min) { to.push(poly); out.push(poly); } }); + return to; } // expensive but worth it? @@ -319,6 +320,9 @@ cleanClipperTree(ctre); filter(fromClipperTree(ctre, z, null, null, min), outA); } + let woutA = filter(geo.poly.diff(setA, setB, z)); + // outA.appendAll(woutA); + // console.log({outA, woutA}); } if (outB) { @@ -334,6 +338,9 @@ cleanClipperTree(ctre); filter(fromClipperTree(ctre, z, null, null, min), outB); } + let woutB = filter(geo.poly.diff(setB, setA, z)); + // outB.appendAll(woutB); + // console.log({outB, woutB}); } return out; diff --git a/src/geo/wasm.js b/src/geo/wasm.js index 8820a503..f2e39483 100644 --- a/src/geo/wasm.js +++ b/src/geo/wasm.js @@ -11,6 +11,7 @@ if (!self.window) (function() { const geo = self.geo; geo.poly = { + diff: polyDiff, union: polyUnion, offset: polyOffset }; @@ -94,6 +95,29 @@ if (!self.window) (function() { return polyNest(out); } + function polyDiff(polysA, polysB, z) { + let wasm = geo.wasm, + memat = wasm.malloc(1024 * 128), + writer = new DataWriter(wasm.heap, memat), + pcountA = 0, + pcountB = 0; + polysA.forEach(poly => pcountA += writePoly(writer, poly)); + polysB.forEach(poly => pcountB += writePoly(writer, poly)); + let resat = wasm.diff(memat, pcountA, pcountB, (pcountA + pcountB) * factor), + reader = new DataReader(wasm.heap, resat), + out = []; + for (;;) { + let poly = readPoly(reader, z); + if (poly) { + out.push(poly); + } else { + break; + } + } + wasm.free(memat); + return polyNest(out); + } + // nest closed polygons without existing parent / child relationships function polyNest(polys) { polys.sort((a,b) => { @@ -149,6 +173,7 @@ if (!self.window) (function() { malloc: exports.mem_get, start: exports._start, free: exports.mem_clr, + diff: exports.poly_diff, union: exports.poly_union, offset: exports.poly_offset }; diff --git a/wasm/kiri-geo.cpp b/wasm/kiri-geo.cpp index e04d8511..cc3057c9 100644 --- a/wasm/kiri-geo.cpp +++ b/wasm/kiri-geo.cpp @@ -94,7 +94,7 @@ Uint32 poly_offset(Uint32 memat, Uint32 polys, float offset) { } __attribute__ ((export_name("poly_union"))) -Uint32 poly_union(Uint32 memat, Uint32 polys) { +Uint32 poly_union(Uint32 memat, Uint32 polys, float offset) { using namespace ClipperLib; @@ -116,9 +116,72 @@ Uint32 poly_union(Uint32 memat, Uint32 polys) { } Clipper clip; - clip.AddPath(ins[0], ptSubject, true); - clip.AddPath(ins[1], ptClip, true); - clip.Execute(ctUnion, outs, pftEvenOdd, pftEvenOdd); + clip.AddPaths(ins, ptSubject, true); + clip.Execute(ctUnion, outs); + + Uint32 resat = pos; + + for (Path po : outs) { + struct length16 *ls = (struct length16 *)(mem + pos); + ls->length = po.size(); + pos += 2; + for (IntPoint pt : po) { + struct point32 *ip = (struct point32 *)(mem + pos); + ip->x = (int)pt.X; + ip->y = (int)pt.Y; + pos += 8; + } + } + + // null terminate + struct length16 *ls = (struct length16 *)(mem + pos); + ls->length = 0; + + clip.Clear(); + + return resat; +} + + + +__attribute__ ((export_name("poly_diff"))) +Uint32 poly_diff(Uint32 memat, Uint32 polysA, Uint32 polysB, float offset) { + + using namespace ClipperLib; + + Paths inA(polysA); + Paths inB(polysB); + Paths outs; + Uint32 pos = memat; + + for (Uint32 poly = 0; poly < polysA; poly++ ) { + struct length16 *ls = (struct length16 *)(mem + pos); + Uint16 points = ls->length; + pos += 2; + while (points-- > 0) { + struct point32 *ip = (struct point32 *)(mem + pos); + pos += 8; + inA[poly] << IntPoint(ip->x, ip->y); + } + poly++; + } + + for (Uint32 poly = 0; poly < polysB; poly++ ) { + struct length16 *ls = (struct length16 *)(mem + pos); + Uint16 points = ls->length; + pos += 2; + while (points-- > 0) { + struct point32 *ip = (struct point32 *)(mem + pos); + pos += 8; + inB[poly] << IntPoint(ip->x, ip->y); + } + poly++; + } + + Clipper clip; + clip.AddPaths(inA, ptSubject, true); + clip.AddPaths(inB, ptClip, true); + clip.Execute(ctDifference, outs, pftEvenOdd, pftEvenOdd); Uint32 resat = pos; diff --git a/wasm/kiri-geo.wasm b/wasm/kiri-geo.wasm index d4525db1d1e6681bc9a86f10898d983ee1eab460..332c1ef12bf53cc58109707fffdc820642605997 100755 GIT binary patch delta 11349 zcmb7K349gRxu5UM+$EVCa>7m$lDRhl5+D%v9WrRx!z!B~i|jY-n=9M^1_1#H2N4t( zlwB4z)TN42s}vDI`?M}t3uv{iwneMeYK!mxow*4~&{tphaqi67zI{30nG4^WPn1|8 zWAdUoWEcjWBBw0Wrll{&f5s%g{>6h~O!E3DHp#$$iqY+p%vfw0#ma6RE38=A^PQU? z6;FypVF*D&2-B=BgprhF7=9yQ*AaDzL_MRvVF<(DKxI&))(tIT5Rr)i!)SyDARtU( znL-3j{0R(Nrr{5mJ|BrxHI4EqO>L#o*QW>k0fPk9GEAz~I9X;Sr^u8LVDl2c-}3o< zhRvbQL?xm`_!B^id;YQG!VVmxB-KZ>D)
  • B`<99GrcYlqZL#qKCF<8Db7#$)YpfCp`fcj0 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