canvas-sketch-util
Version:
Utilities for sketching in Canvas, WebGL and generative art
174 lines (161 loc) • 5.39 kB
JavaScript
var lineclip = require('lineclip');
var almostEqual = require('almost-equal');
var arrayAlmostEqual = require('array-almost-equal');
var clone = require('clone');
var squaredDistance = require('./lib/vec2').squaredDistance;
module.exports.arePointsCollinear = function (point0, point1, point2) {
var x0 = point0[0];
var y0 = point0[1];
var x1 = point1[0];
var y1 = point1[1];
var x2 = point2[0];
var y2 = point2[1];
return almostEqual((y0 - y1) * (x0 - x2), (y0 - y2) * (x0 - x1));
};
module.exports.removeDuplicatePoints = function (path) {
var newPath = [];
var lastPoint;
for (var i = 0; i < path.length; i++) {
var curPoint = path[i];
if (!lastPoint || !arrayAlmostEqual(lastPoint, curPoint)) {
newPath.push(curPoint);
lastPoint = curPoint;
}
}
return clone(newPath);
};
module.exports.removeCollinearPoints = function (path) {
var newPath = [];
var remainingPoints = clone(path);
while (remainingPoints.length >= 3) {
var p0 = remainingPoints[0];
var p1 = remainingPoints[1];
var p2 = remainingPoints[2];
var collinear = module.exports.arePointsCollinear(p0, p1, p2);
// one more check is to ensure that points are in a line:
// A->B->C
// not A->C->B or some variant
if (collinear) {
var distAB = squaredDistance(p0, p1);
var distAC = squaredDistance(p0, p2);
if (distAB > distAC) collinear = false;
}
if (collinear) {
// the first 3 points are collinear
// remove the second point as it isn't needed
remainingPoints.splice(1, 1);
} else {
// the 3 points are not collinear
// add the first one as the others may still be collinear
for (var i = 0; i < 1; i++) {
newPath.push(remainingPoints.shift());
}
}
}
// add any remaining points
while (remainingPoints.length) {
newPath.push(remainingPoints.shift());
}
return newPath;
};
module.exports.clipSegmentToCircle = require('./lib/clip/clip-segment-to-circle');
module.exports.clipLineToCircle = require('./lib/clip/clip-line-to-circle');
module.exports.clipPolylinesToBox = function (polylines, bbox, border, closeLines) {
if (!Array.isArray(bbox) || (bbox.length !== 2 && bbox.length !== 4)) {
throw new Error('Expected box to either be format [ minPoint, maxPoint ] or [ minX, minY, maxX, maxY ]');
}
// Expand nested format to flat bounds
if (bbox.length === 2) {
var min = bbox[0];
var max = bbox[1];
bbox = [ min[0], min[1], max[0], max[1] ];
}
closeLines = closeLines !== false;
border = Boolean(border);
if (border) {
return polylines.map(function (line) {
var result = lineclip.polygon(line, bbox);
if (closeLines && result.length > 2) {
result.push(result[0]);
}
return result;
}).filter(function (lines) {
return lines.length > 0;
});
} else {
return polylines.map(function (line) {
return lineclip.polyline(line, bbox);
}).reduce(function (a, b) {
return a.concat(b);
}, []);
}
};
module.exports.createHatchLines = createHatchLines;
function createHatchLines (bounds, angle, spacing, out) {
if (!Array.isArray(bounds) || (bounds.length !== 2 && bounds.length !== 4)) {
throw new Error('Expected box to either be format [ minPoint, maxPoint ] or [ minX, minY, maxX, maxY ]');
}
// Expand nested format to flat bounds
if (bounds.length === 2) {
var min = bounds[0];
var max = bounds[1];
bounds = [ min[0], min[1], max[0], max[1] ];
}
if (angle == null) angle = -Math.PI / 4;
if (spacing == null) spacing = 0.5;
if (out == null) out = [];
// Reference:
// https://github.com/evil-mad/EggBot/blob/master/inkscape_driver/eggbot_hatch.py
spacing = Math.abs(spacing);
if (spacing === 0) throw new Error('cannot use a spacing of zero as it will run an infinite loop!');
var xmin = bounds[0];
var ymin = bounds[1];
var xmax = bounds[2];
var ymax = bounds[3];
var w = xmax - xmin;
var h = ymax - ymin;
if (w === 0 || h === 0) return out;
var r = Math.sqrt(w * w + h * h) / 2;
var rotAngle = Math.PI / 2 - angle;
var ca = Math.cos(rotAngle);
var sa = Math.sin(rotAngle);
var cx = xmin + (w / 2);
var cy = ymin + (h / 2);
var i = -r;
while (i <= r) {
// Line starts at (i, -r) and goes to (i, +r)
var x1 = cx + (i * ca) + (r * sa); // i * ca - (-r) * sa
var y1 = cy + (i * sa) - (r * ca); // i * sa + (-r) * ca
var x2 = cx + (i * ca) - (r * sa); // i * ca - (+r) * sa
var y2 = cy + (i * sa) + (r * ca); // i * sa + (+r) * ca
i += spacing;
// Remove any potential hatch lines which are entirely
// outside of the bounding box
if ((x1 < xmin && x2 < xmin) || (x1 > xmax && x2 > xmax)) {
continue;
}
if ((y1 < ymin && y2 < ymin) || (y1 > ymax && y2 > ymax)) {
continue;
}
out.push([ [ x1, y1 ], [ x2, y2 ] ]);
}
return out;
}
module.exports.getBounds = function getBounds (points) {
var n = points.length;
if (n === 0) {
throw new Error('Expected points to be a non-empty array');
}
var d = points[0].length;
var lo = points[0].slice();
var hi = points[0].slice();
for (var i = 1; i < n; ++i) {
var p = points[i];
for (var j = 0; j < d; ++j) {
var x = p[j];
lo[j] = Math.min(lo[j], x);
hi[j] = Math.max(hi[j], x);
}
}
return [ lo, hi ];
};