holes-in
Version:
Generates a 3D mesh from a 2D outer path and 2D inner paths
306 lines (262 loc) • 9.08 kB
JavaScript
;
const clipperLib = require("clipper-lib");
const constants = require("./constants.js");
const pathHelper = {
/**
* Compute the xor of two arrays of path
*
*/
getXorOfPaths(pathsSubj, pathsClip) {
const options = {
subjectFillType: clipperLib.PolyFillType.pftNonZero,
clipFillType: clipperLib.PolyFillType.pftNonZero,
clipType: clipperLib.ClipType.ctXor,
};
return pathHelper.executeClipper(pathsSubj, pathsClip, options);
},
/**
* Compute the xor of two arrays of path
*
*/
getUnionOfPaths(pathsSubj, pathsClip, options) {
options = Object.assign({}, options);
options.clipType = clipperLib.ClipType.ctUnion;
return pathHelper.executeClipper(pathsSubj, pathsClip, options);
},
/**
* Compute the xor of two arrays of path
*
*/
getDiffOfPaths(pathsSubj, pathsClip, options) {
options = Object.assign({}, options);
options.clipType = clipperLib.ClipType.ctDifference;
return pathHelper.executeClipper(pathsSubj, pathsClip, options);
},
/**
* Compute the xor of two arrays of path
*
*/
getInterOfPaths(pathsSubj, pathsClip, op) {
const options = {
subjectFillType: clipperLib.PolyFillType.pftNonZero,
clipFillType: clipperLib.PolyFillType.pftNonZero,
clipType: clipperLib.ClipType.ctIntersection,
};
return pathHelper.executeClipper(pathsSubj, pathsClip, options);
},
/**
* Simplify an array of paths
*
*/
simplifyPaths(paths, options = {
fillType: clipperLib.PolyFillType.pftNonZero,
}) {
return clipperLib.Clipper.SimplifyPolygons(paths, options.fillType);
},
simplifyPath(path, options = {
fillType: clipperLib.PolyFillType.pftNonZero,
}) {
return clipperLib.Clipper.SimplifyPolygon(path, options.fillType);
},
/**
* Apply Clipper operation to pathsSubj and pathClip
* clipType: {ctIntersection,ctUnion,ctDifference,ctXor}
* subjectFill: {pftEvenOdd,pftNonZero,pftPositive,pftNegative}
* clipFill: same as upon
*/
executeClipper(pathsSubj, pathsClip, options) {
options = Object.assign({
clipType: clipperLib.ClipType.ctUnion,
subjectFill: clipperLib.PolyFillType.pftNonZero,
clipFill: clipperLib.PolyFillType.pftNonZero,
polyTree: false,
pathPreProcess: true,
}, options);
if (!pathsSubj && !pathsClip) {
return;
}
if(options.pathPreProcess){
pathHelper.setDirectionPaths(pathsSubj, -1);
if (pathsClip) {
pathHelper.setDirectionPaths(pathsClip, -1);
}
}
const cpr = new clipperLib.Clipper();
cpr.StrictlySimple = true;
cpr.AddPaths(pathsSubj, clipperLib.PolyType.ptSubject, true);
if (pathsClip) {
cpr.AddPaths(pathsClip, clipperLib.PolyType.ptClip, true);
}
let res;
if(options.polyTree){
res = new clipperLib.PolyTree();
} else {
res = new clipperLib.Paths();
}
cpr.Execute(options.clipType, res, options.subjectFill, options.clipFill);
return res;
},
offsetPaths(paths, offset = 100) {
let co = new clipperLib.ClipperOffset();
let res = new clipperLib.Paths();
co.AddPaths(paths, clipperLib.JoinType.jtMiter, clipperLib.EndType.etClosedPolygon);
co.MiterLimit = 2;
co.ArcTolerance = 0.25;
co.Execute(res, offset);
return res;
},
offsetPath(path, offset = 100) {
return pathHelper.offsetPaths([path], offset)[0];
},
/**
* sets the direction of an array of path
*/
setDirectionPaths(paths, direction) {
for (let i = 0; i < paths.length; i++) {
pathHelper.setDirectionPath(paths[i], direction);
}
},
/**
* sets the direction of a path
*/
setDirectionPath(path, direction) {
if (clipperLib.JS.AreaOfPolygon(path) * direction < 0) {
path.reverse();
}
},
/**
* checks if the signed area of the path is positive
*/
isPositivePath(path) {
return pathHelper.getDirectionPath(path) > 0;
},
/**
* checks if the signed area of the path is negative
*/
isNegativePath(path) {
return pathHelper.getDirectionPath(path) < 0;
},
/**
* get the direction of an arary of path
*/
getDirectionPaths(paths) {
return paths.map(pathHelper.getDirectionPath);
},
/**
* get the direction of a path
*/
getDirectionPath(path) {
return (clipperLib.JS.AreaOfPolygon(path) > 0) ? 1 : -1;
},
scaleUpPaths(paths, scale = constants.scaleFactor) {
clipperLib.JS.ScaleUpPaths(paths, scale);
},
scaleUpPath(path, scale = constants.scaleFactor) {
clipperLib.JS.ScaleUpPath(path, scale);
},
scaleDownPath(path, scale = constants.scaleFactor) {
clipperLib.JS.ScaleDownPath(path, scale);
},
scaleDownPaths(paths, scale = constants.scaleFactor) {
clipperLib.JS.ScaleDownPaths(paths, scale);
},
getMatchingEdgeIndex(path, pathToMatch, offset = 0) {
let prevAligned = false;
let res = {};
for (let i = path.length - 1 - offset; i >= 0; i--) {
for (let j = pathToMatch.length - 1; j >= 0; j--) {
const p1 = pathToMatch[j];
const p2 = pathToMatch[(j + 1) % pathToMatch.length];
const currAlgigned = pathHelper.isApproxAligned(path[i], path[(i + 1) % path.length], p1, p2);
if (!currAlgigned && prevAligned) {
return res;
}
if (!currAlgigned && !prevAligned) { continue; }
if (currAlgigned) {
res = {
index: i,
pointmatch: p1,
};
prevAligned = currAlgigned;
break;
}
}
}
},
normalizeVec(edge) {
const norm = Math.sqrt(edge.X * edge.X + edge.Y * edge.Y);
return { X: edge.X / norm, Y: edge.Y / norm };
},
isApproxAligned(e11, e12, e21, e22, epsilon = 0.0001) {
// checks the area of the triangles:
// [ Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By) ] / 2
const area1 = e11.X * (e12.Y - e21.Y) + e12.X * (e21.Y - e11.Y) + e21.X * (e11.Y - e12.Y);
// [ Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By) ] / 2
const area2 = e11.X * (e12.Y - e22.Y) + e12.X * (e22.Y - e11.Y) + e22.X * (e11.Y - e12.Y);
const lengthAB = (e11.X - e12.X) * (e11.X - e12.X) + (e11.Y - e12.Y) * (e11.Y - e12.Y);
const lengthAC1 = (e11.X - e21.X) * (e11.X - e21.X) + (e11.Y - e21.Y) * (e11.Y - e21.Y);
const lengthAC2 = (e11.X - e22.X) * (e11.X - e22.X) + (e11.Y - e22.Y) * (e11.Y - e22.Y);
return (Math.abs(area1) / (lengthAB + lengthAC1) + Math.abs(area2) / (lengthAB + lengthAC2))< epsilon;
},
getEdge(point1, point2) {
return {
X: (point2.X - point1.X),
Y: (point2.Y - point1.Y),
};
},
/**
* returns the index of the point in path matching with point
*
*/
getPointMatch(path, point) {
for (let i = 0; i < path.length; i++) {
if (pathHelper.isEqual(path[i], point)) {
return {
index: i,
};
}
}
},
getPointsOnEdge(path, edge) {
const res = [];
for (let i = 0; i < path.length; i++) {
const ptA = path[i];
const ptB = path[(i + 1) % path.length];
if (!pathHelper.isApproxAligned(ptA, ptB, edge[0], edge[1])) {
continue;
}
res.push(ptA);
}
return res;
},
getNorm(point) {
return Math.sqrt(point.X * point.X + point.Y * point.Y);
},
getDistance(point1, point2) {
const edge = pathHelper.getEdge(point1, point2);
return pathHelper.getNorm(edge);
},
getTotalLength(path) {
let res = 0;
if (!path) {
return 0;
}
for (let i = 0; i < path.length; i++) {
const curr = path[i];
const next = path[(i + 1) % path.length];
res += pathHelper.getDistance(curr, next);
}
return res;
},
/**
* checks if two points have the same coordinates
*
*/
isEqual(point1, point2) {
return (point1.X === point2.X) && (point1.Y === point2.Y);
},
cleanPaths(paths, threshold = 10) {
return clipperLib.Clipper.CleanPolygons(paths, threshold);
},
};
module.exports = pathHelper;