396 lines
9.3 KiB
JavaScript
396 lines
9.3 KiB
JavaScript
var PathProxy = require("../core/PathProxy");
|
|
|
|
var line = require("./line");
|
|
|
|
var cubic = require("./cubic");
|
|
|
|
var quadratic = require("./quadratic");
|
|
|
|
var arc = require("./arc");
|
|
|
|
var _util = require("./util");
|
|
|
|
var normalizeRadian = _util.normalizeRadian;
|
|
|
|
var curve = require("../core/curve");
|
|
|
|
var windingLine = require("./windingLine");
|
|
|
|
var CMD = PathProxy.CMD;
|
|
var PI2 = Math.PI * 2;
|
|
var EPSILON = 1e-4;
|
|
|
|
function isAroundEqual(a, b) {
|
|
return Math.abs(a - b) < EPSILON;
|
|
} // 临时数组
|
|
|
|
|
|
var roots = [-1, -1, -1];
|
|
var extrema = [-1, -1];
|
|
|
|
function swapExtrema() {
|
|
var tmp = extrema[0];
|
|
extrema[0] = extrema[1];
|
|
extrema[1] = tmp;
|
|
}
|
|
|
|
function windingCubic(x0, y0, x1, y1, x2, y2, x3, y3, x, y) {
|
|
// Quick reject
|
|
if (y > y0 && y > y1 && y > y2 && y > y3 || y < y0 && y < y1 && y < y2 && y < y3) {
|
|
return 0;
|
|
}
|
|
|
|
var nRoots = curve.cubicRootAt(y0, y1, y2, y3, y, roots);
|
|
|
|
if (nRoots === 0) {
|
|
return 0;
|
|
} else {
|
|
var w = 0;
|
|
var nExtrema = -1;
|
|
var y0_;
|
|
var y1_;
|
|
|
|
for (var i = 0; i < nRoots; i++) {
|
|
var t = roots[i]; // Avoid winding error when intersection point is the connect point of two line of polygon
|
|
|
|
var unit = t === 0 || t === 1 ? 0.5 : 1;
|
|
var x_ = curve.cubicAt(x0, x1, x2, x3, t);
|
|
|
|
if (x_ < x) {
|
|
// Quick reject
|
|
continue;
|
|
}
|
|
|
|
if (nExtrema < 0) {
|
|
nExtrema = curve.cubicExtrema(y0, y1, y2, y3, extrema);
|
|
|
|
if (extrema[1] < extrema[0] && nExtrema > 1) {
|
|
swapExtrema();
|
|
}
|
|
|
|
y0_ = curve.cubicAt(y0, y1, y2, y3, extrema[0]);
|
|
|
|
if (nExtrema > 1) {
|
|
y1_ = curve.cubicAt(y0, y1, y2, y3, extrema[1]);
|
|
}
|
|
}
|
|
|
|
if (nExtrema === 2) {
|
|
// 分成三段单调函数
|
|
if (t < extrema[0]) {
|
|
w += y0_ < y0 ? unit : -unit;
|
|
} else if (t < extrema[1]) {
|
|
w += y1_ < y0_ ? unit : -unit;
|
|
} else {
|
|
w += y3 < y1_ ? unit : -unit;
|
|
}
|
|
} else {
|
|
// 分成两段单调函数
|
|
if (t < extrema[0]) {
|
|
w += y0_ < y0 ? unit : -unit;
|
|
} else {
|
|
w += y3 < y0_ ? unit : -unit;
|
|
}
|
|
}
|
|
}
|
|
|
|
return w;
|
|
}
|
|
}
|
|
|
|
function windingQuadratic(x0, y0, x1, y1, x2, y2, x, y) {
|
|
// Quick reject
|
|
if (y > y0 && y > y1 && y > y2 || y < y0 && y < y1 && y < y2) {
|
|
return 0;
|
|
}
|
|
|
|
var nRoots = curve.quadraticRootAt(y0, y1, y2, y, roots);
|
|
|
|
if (nRoots === 0) {
|
|
return 0;
|
|
} else {
|
|
var t = curve.quadraticExtremum(y0, y1, y2);
|
|
|
|
if (t >= 0 && t <= 1) {
|
|
var w = 0;
|
|
var y_ = curve.quadraticAt(y0, y1, y2, t);
|
|
|
|
for (var i = 0; i < nRoots; i++) {
|
|
// Remove one endpoint.
|
|
var unit = roots[i] === 0 || roots[i] === 1 ? 0.5 : 1;
|
|
var x_ = curve.quadraticAt(x0, x1, x2, roots[i]);
|
|
|
|
if (x_ < x) {
|
|
// Quick reject
|
|
continue;
|
|
}
|
|
|
|
if (roots[i] < t) {
|
|
w += y_ < y0 ? unit : -unit;
|
|
} else {
|
|
w += y2 < y_ ? unit : -unit;
|
|
}
|
|
}
|
|
|
|
return w;
|
|
} else {
|
|
// Remove one endpoint.
|
|
var unit = roots[0] === 0 || roots[0] === 1 ? 0.5 : 1;
|
|
var x_ = curve.quadraticAt(x0, x1, x2, roots[0]);
|
|
|
|
if (x_ < x) {
|
|
// Quick reject
|
|
return 0;
|
|
}
|
|
|
|
return y2 < y0 ? unit : -unit;
|
|
}
|
|
}
|
|
} // TODO
|
|
// Arc 旋转
|
|
|
|
|
|
function windingArc(cx, cy, r, startAngle, endAngle, anticlockwise, x, y) {
|
|
y -= cy;
|
|
|
|
if (y > r || y < -r) {
|
|
return 0;
|
|
}
|
|
|
|
var tmp = Math.sqrt(r * r - y * y);
|
|
roots[0] = -tmp;
|
|
roots[1] = tmp;
|
|
var diff = Math.abs(startAngle - endAngle);
|
|
|
|
if (diff < 1e-4) {
|
|
return 0;
|
|
}
|
|
|
|
if (diff % PI2 < 1e-4) {
|
|
// Is a circle
|
|
startAngle = 0;
|
|
endAngle = PI2;
|
|
var dir = anticlockwise ? 1 : -1;
|
|
|
|
if (x >= roots[0] + cx && x <= roots[1] + cx) {
|
|
return dir;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (anticlockwise) {
|
|
var tmp = startAngle;
|
|
startAngle = normalizeRadian(endAngle);
|
|
endAngle = normalizeRadian(tmp);
|
|
} else {
|
|
startAngle = normalizeRadian(startAngle);
|
|
endAngle = normalizeRadian(endAngle);
|
|
}
|
|
|
|
if (startAngle > endAngle) {
|
|
endAngle += PI2;
|
|
}
|
|
|
|
var w = 0;
|
|
|
|
for (var i = 0; i < 2; i++) {
|
|
var x_ = roots[i];
|
|
|
|
if (x_ + cx > x) {
|
|
var angle = Math.atan2(y, x_);
|
|
var dir = anticlockwise ? 1 : -1;
|
|
|
|
if (angle < 0) {
|
|
angle = PI2 + angle;
|
|
}
|
|
|
|
if (angle >= startAngle && angle <= endAngle || angle + PI2 >= startAngle && angle + PI2 <= endAngle) {
|
|
if (angle > Math.PI / 2 && angle < Math.PI * 1.5) {
|
|
dir = -dir;
|
|
}
|
|
|
|
w += dir;
|
|
}
|
|
}
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
function containPath(data, lineWidth, isStroke, x, y) {
|
|
var w = 0;
|
|
var xi = 0;
|
|
var yi = 0;
|
|
var x0 = 0;
|
|
var y0 = 0;
|
|
|
|
for (var i = 0; i < data.length;) {
|
|
var cmd = data[i++]; // Begin a new subpath
|
|
|
|
if (cmd === CMD.M && i > 1) {
|
|
// Close previous subpath
|
|
if (!isStroke) {
|
|
w += windingLine(xi, yi, x0, y0, x, y);
|
|
} // 如果被任何一个 subpath 包含
|
|
// if (w !== 0) {
|
|
// return true;
|
|
// }
|
|
|
|
}
|
|
|
|
if (i === 1) {
|
|
// 如果第一个命令是 L, C, Q
|
|
// 则 previous point 同绘制命令的第一个 point
|
|
//
|
|
// 第一个命令为 Arc 的情况下会在后面特殊处理
|
|
xi = data[i];
|
|
yi = data[i + 1];
|
|
x0 = xi;
|
|
y0 = yi;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case CMD.M:
|
|
// moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
|
|
// 在 closePath 的时候使用
|
|
x0 = data[i++];
|
|
y0 = data[i++];
|
|
xi = x0;
|
|
yi = y0;
|
|
break;
|
|
|
|
case CMD.L:
|
|
if (isStroke) {
|
|
if (line.containStroke(xi, yi, data[i], data[i + 1], lineWidth, x, y)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
// NOTE 在第一个命令为 L, C, Q 的时候会计算出 NaN
|
|
w += windingLine(xi, yi, data[i], data[i + 1], x, y) || 0;
|
|
}
|
|
|
|
xi = data[i++];
|
|
yi = data[i++];
|
|
break;
|
|
|
|
case CMD.C:
|
|
if (isStroke) {
|
|
if (cubic.containStroke(xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], lineWidth, x, y)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
w += windingCubic(xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], x, y) || 0;
|
|
}
|
|
|
|
xi = data[i++];
|
|
yi = data[i++];
|
|
break;
|
|
|
|
case CMD.Q:
|
|
if (isStroke) {
|
|
if (quadratic.containStroke(xi, yi, data[i++], data[i++], data[i], data[i + 1], lineWidth, x, y)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
w += windingQuadratic(xi, yi, data[i++], data[i++], data[i], data[i + 1], x, y) || 0;
|
|
}
|
|
|
|
xi = data[i++];
|
|
yi = data[i++];
|
|
break;
|
|
|
|
case CMD.A:
|
|
// TODO Arc 判断的开销比较大
|
|
var cx = data[i++];
|
|
var cy = data[i++];
|
|
var rx = data[i++];
|
|
var ry = data[i++];
|
|
var theta = data[i++];
|
|
var dTheta = data[i++]; // TODO Arc 旋转
|
|
|
|
i += 1;
|
|
var anticlockwise = 1 - data[i++];
|
|
var x1 = Math.cos(theta) * rx + cx;
|
|
var y1 = Math.sin(theta) * ry + cy; // 不是直接使用 arc 命令
|
|
|
|
if (i > 1) {
|
|
w += windingLine(xi, yi, x1, y1, x, y);
|
|
} else {
|
|
// 第一个命令起点还未定义
|
|
x0 = x1;
|
|
y0 = y1;
|
|
} // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放
|
|
|
|
|
|
var _x = (x - cx) * ry / rx + cx;
|
|
|
|
if (isStroke) {
|
|
if (arc.containStroke(cx, cy, ry, theta, theta + dTheta, anticlockwise, lineWidth, _x, y)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
w += windingArc(cx, cy, ry, theta, theta + dTheta, anticlockwise, _x, y);
|
|
}
|
|
|
|
xi = Math.cos(theta + dTheta) * rx + cx;
|
|
yi = Math.sin(theta + dTheta) * ry + cy;
|
|
break;
|
|
|
|
case CMD.R:
|
|
x0 = xi = data[i++];
|
|
y0 = yi = data[i++];
|
|
var width = data[i++];
|
|
var height = data[i++];
|
|
var x1 = x0 + width;
|
|
var y1 = y0 + height;
|
|
|
|
if (isStroke) {
|
|
if (line.containStroke(x0, y0, x1, y0, lineWidth, x, y) || line.containStroke(x1, y0, x1, y1, lineWidth, x, y) || line.containStroke(x1, y1, x0, y1, lineWidth, x, y) || line.containStroke(x0, y1, x0, y0, lineWidth, x, y)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
// FIXME Clockwise ?
|
|
w += windingLine(x1, y0, x1, y1, x, y);
|
|
w += windingLine(x0, y1, x0, y0, x, y);
|
|
}
|
|
|
|
break;
|
|
|
|
case CMD.Z:
|
|
if (isStroke) {
|
|
if (line.containStroke(xi, yi, x0, y0, lineWidth, x, y)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
// Close a subpath
|
|
w += windingLine(xi, yi, x0, y0, x, y); // 如果被任何一个 subpath 包含
|
|
// FIXME subpaths may overlap
|
|
// if (w !== 0) {
|
|
// return true;
|
|
// }
|
|
}
|
|
|
|
xi = x0;
|
|
yi = y0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isStroke && !isAroundEqual(yi, y0)) {
|
|
w += windingLine(xi, yi, x0, y0, x, y) || 0;
|
|
}
|
|
|
|
return w !== 0;
|
|
}
|
|
|
|
function contain(pathData, x, y) {
|
|
return containPath(pathData, 0, false, x, y);
|
|
}
|
|
|
|
function containStroke(pathData, lineWidth, x, y) {
|
|
return containPath(pathData, lineWidth, true, x, y);
|
|
}
|
|
|
|
exports.contain = contain;
|
|
exports.containStroke = containStroke; |