From 1b9a18fb82d481ecd165c55d71532a8c94a36bcb Mon Sep 17 00:00:00 2001 From: kekee000 Date: Fri, 27 Feb 2015 23:59:19 +0800 Subject: [PATCH] path split by segment --- src/editor/command/join.js | 2 +- src/graphics/join/interpolatePathByJoint.js | 102 +++++++++++++++++ ...Joint.js => interpolatePathCrossBezier.js} | 69 +---------- src/graphics/pathJoin.js | 10 +- src/graphics/pathSplitBySegment.js | 108 +++++++++++++----- src/graphics/pathUtil.js | 14 +-- src/graphics/util.js | 15 ++- 7 files changed, 205 insertions(+), 115 deletions(-) create mode 100644 src/graphics/join/interpolatePathByJoint.js rename src/graphics/join/{interpolatePathJoint.js => interpolatePathCrossBezier.js} (69%) diff --git a/src/editor/command/join.js b/src/editor/command/join.js index 8969c69..a313982 100644 --- a/src/editor/command/join.js +++ b/src/editor/command/join.js @@ -126,7 +126,7 @@ define( var bound = computeBoundingBox.computePath(shapes[i].points); // 判断是否相交 if (isBoundingBoxSegmentCross(bound, p0, p1)) { - shapesCross.push(shapes); + shapesCross.push(shapes[i]); } } diff --git a/src/graphics/join/interpolatePathByJoint.js b/src/graphics/join/interpolatePathByJoint.js new file mode 100644 index 0000000..e12fadf --- /dev/null +++ b/src/graphics/join/interpolatePathByJoint.js @@ -0,0 +1,102 @@ +/** + * @file 根据路径交点对路径进行插值, + * 对bezier曲线上的交点分割bezier曲线,对直线分割成直线 + * @author mengke01(kekee000@gmail.com) + */ + + +define( + function (require) { + + var bezierQ2Split = require('math/bezierQ2Split'); + var getBezierQ2T = require('math/getBezierQ2T'); + var util = require('graphics/util'); + var ceilPoint = util.ceilPoint; + + /** + * 点分割贝塞尔曲线 + * @param {Object} p0 p0 + * @param {Object} p1 p1 + * @param {Object} p2 p2 + * @param {Array} points 点数组 + * @return {Array} 分割后的结果 + */ + function bezierSplitByPoints(p0, p1, p2, points) { + var p; + var result = [p0]; + var bezierArray; + var pp0 = p0; + var pp1 = p1; + + // 对交点按照t值从小到大排序的点 + points = points.map(function (p) { + p.t = getBezierQ2T(p0, p1, p2, p); + return p; + }).sort(function (a, b) { + return a.t - b.t; + }); + + while(p = points.shift()) { + bezierArray = bezierQ2Split(pp0, pp1, p2, p); + if (bezierArray[1]) { + bezierArray[0][2].onCurve = true; + result.push(bezierArray[0][1]); + result.push(bezierArray[0][2]); + pp0 = bezierArray[1][0]; + pp1 = bezierArray[1][1]; + } + } + result.push(pp1); + result.push(p2); + return result.map(function (p) { + return ceilPoint(p); + }); + } + + /** + * 对路径进按分割点进行插值 + * @param {Array} path 路径点集 + * @param {Array} joints 交点集 + * @example + * + * joint = [ + * { + * "index": 1, + * "points": { + * "x": 1, + * "y": 1 + * } + * } + * ] + * + * @return {Array} 插值后的路径 + */ + function interpolatePathByJoint(path, joints) { + var splice = Array.prototype.splice; + joints.sort(function (a, b) { + return b.index - a.index; + }).forEach(function (joint) { + var i = joint.index; + var cur = path[i]; + // 分割bezier曲线 + if (!cur.onCurve) { + var prev = i === 0 ? path[path.length - 1] : path[i - 1]; + var next = i === path.length - 1 ? path[0] : path[i + 1]; + var result = bezierSplitByPoints(prev, cur, next, joint.points); + splice.apply(path, [i, 1].concat(result.slice(1, result.length - 1))); + } + // 分割直线 + else { + splice.apply(path, [i, 0].concat(joint.points.map(function (p) { + p.onCurve = true; + return p; + }))); + } + }); + return path; + } + + + return interpolatePathByJoint; + } +); diff --git a/src/graphics/join/interpolatePathJoint.js b/src/graphics/join/interpolatePathCrossBezier.js similarity index 69% rename from src/graphics/join/interpolatePathJoint.js rename to src/graphics/join/interpolatePathCrossBezier.js index 4f1fffb..70457c4 100644 --- a/src/graphics/join/interpolatePathJoint.js +++ b/src/graphics/join/interpolatePathCrossBezier.js @@ -9,52 +9,9 @@ define( var isBezierSegmentCross = require('graphics/isBezierSegmentCross'); var isBezierCross = require('graphics/isBezierCross'); - var bezierQ2Split = require('math/bezierQ2Split'); - var getBezierQ2Point = require('math/getBezierQ2Point'); - var getBezierQ2T = require('math/getBezierQ2T'); + var interpolatePathByJoint = require('graphics/join/interpolatePathByJoint'); var util = require('graphics/util'); var isPointOverlap = util.isPointOverlap; - var ceilPoint = util.ceilPoint; - - /** - * 点分割贝塞尔曲线 - * @param {Object} p0 p0 - * @param {Object} p1 p1 - * @param {Object} p2 p2 - * @param {Array} points 点数组 - * @return {Array} 分割后的结果 - */ - function bezierSplitByPoints(p0, p1, p2, points) { - var p; - var result = [p0]; - var bezierArray; - var pp0 = p0; - var pp1 = p1; - - // 对交点按照t值从小到大排序的点 - points = points.map(function (p) { - p.t = getBezierQ2T(p0, p1, p2, p); - return p; - }).sort(function (a, b) { - return a.t - b.t; - }); - - while(p = points.shift()) { - bezierArray = bezierQ2Split(pp0, pp1, p2, p); - if (bezierArray[1]) { - bezierArray[0][2].onCurve = true; - result.push(bezierArray[0][1]); - result.push(bezierArray[0][2]); - pp0 = bezierArray[1][0]; - pp1 = bezierArray[1][1]; - } - } - result.push(pp1); - result.push(p2); - return result.map(function (p) { - return ceilPoint(p); - }); - } /** * 线段分割贝塞尔曲线 @@ -123,29 +80,13 @@ define( }); } - function splitBezier(path, joints) { - var splice = Array.prototype.splice; - joints.sort(function (a, b) { - return b.index - a.index; - }).forEach(function (joint) { - var i = joint.index; - var cur = path[i]; - var prev = i === 0 ? path[path.length - 1] : path[i - 1]; - var next = i === path.length - 1 ? path[0] : path[i + 1]; - var result = bezierSplitByPoints(prev, cur, next, joint.points); - splice.apply(path, [i, 1].concat(result.slice(1, result.length - 1))); - }); - return path; - } - - /** * 将bezier曲线和直线,bezier曲线之间相交部分进行插值分段 * @param {Array} subjectPath 主路径 * @param {Array} clipPath 剪切路径 * @return {Array} 相交的点集 */ - function interpolatePathJoint (subjectPath, clipPath) { + function interpolatePathCrossBezier (subjectPath, clipPath) { var isSelfInterpolate = subjectPath === clipPath; var curPointSubject; @@ -202,15 +143,15 @@ define( } } - splitBezier(subjectPath, popJoints(subjectJoints)); + interpolatePathByJoint(subjectPath, popJoints(subjectJoints)); // 如果不是自相交则对 clip path 进行插值 if (subjectJoints !== clipJoints) { - splitBezier(clipPath, popJoints(clipJoints)); + interpolatePathByJoint(clipPath, popJoints(clipJoints)); } } - return interpolatePathJoint; + return interpolatePathCrossBezier; } ); diff --git a/src/graphics/pathJoin.js b/src/graphics/pathJoin.js index 74e849c..b7a1870 100644 --- a/src/graphics/pathJoin.js +++ b/src/graphics/pathJoin.js @@ -20,8 +20,9 @@ define( var util = require('graphics/util'); var ceilPoint = util.ceilPoint; + var getPointHash = util.getPointHash; - var interpolatePathJoint = require('./join/interpolatePathJoint'); + var interpolatePathCrossBezier = require('./join/interpolatePathCrossBezier'); var getBezierQ2Point = require('math/getBezierQ2Point'); @@ -63,11 +64,6 @@ define( } - function getPointHash(p) { - return Math.floor(7 * Math.floor(p.x * 100) + 31 * Math.floor(p.y * 100)); - } - - /** * 求路径交集、并集、差集 * @@ -97,7 +93,7 @@ define( // 计算bezier、直线的交点,分割bezier曲线 for (i = 0, l = paths.length; i < l; i++) { for (var j = i; j < l; j++) { - interpolatePathJoint(paths[i], paths[j]); + interpolatePathCrossBezier(paths[i], paths[j]); } } diff --git a/src/graphics/pathSplitBySegment.js b/src/graphics/pathSplitBySegment.js index 4ce905b..bf39f37 100644 --- a/src/graphics/pathSplitBySegment.js +++ b/src/graphics/pathSplitBySegment.js @@ -10,13 +10,14 @@ define( function (require) { - var computeBoundingBox = require('./computeBoundingBox'); var getSegmentPathJoint = require('./join/getSegmentPathJoint'); var pathSplit = require('./join/pathSplit'); - var isBoundingBoxSegmentCross = require('./isBoundingBoxSegmentCross'); var util = require('./pathUtil'); var interpolate = util.interpolate; var deInterpolate = util.deInterpolate; + var util = require('graphics/util'); + var isPointOverlap = util.isPointOverlap; + var getPointHash = util.getPointHash; /** * 线段切割路径 @@ -30,41 +31,88 @@ define( var i = 0; var l = paths.length; + var leftSide = []; + var rightSide = []; + var jointArray = []; + for (; i < l; i++) { - paths[i] = interpolate(paths[i]); - var result = getSegmentPathJoint(paths[i], p0, p1); - console.log(result); + var path = interpolate(paths[i]); + var result = getSegmentPathJoint(path, p0, p1); + if (result) { + + var splitedPaths = pathSplit(path, result.map(function (p) { + + p.from = i; + p.index = p.index1; + + var cur = path[p.index]; + var next = path[p.index === path.length - 1 ? 0 : p.index + 1]; + // bezier曲线起始点 + if (!cur.onCurve) { + cur = path[p.index === 0 ? path.length - 1 : p.index - 1]; + next = path[p.index === path.length - 1 ? 0 : p.index + 1]; + } + // 求zCount + p.zCount = (p.zCount || 0) + + ( + (p.x - cur.x > 0 && p.x - next.x < 0) + || Math.abs(p.x - cur.x) < 0.001 && (p.y - cur.y > 0 && p.y - next.y < 0) + ? -1 : 1 + ); + jointArray.push(p); + + return p; + })); + + // 将分割后的路径分为左右两组 + // http://blog.csdn.net/modiz/article/details/9928955 + splitedPaths.forEach(function (path) { + var p2 = path[1]; + if ((p0.x - p2.x) * (p1.y - p2.y) - (p0.y - p2.y) * (p1.x - p2.x) > 0) { + leftSide.push(path); + } + else { + rightSide.push(path); + } + }); + } } - // 有交点 - // if (result && result.length > 1) { + if (jointArray.length <= 1) { + return paths; + } - // var splitedPaths = pathSplit(path, result.map(function (p) { - // p.index = p.index1; - // return p; - // })); + // 交点按照距离原点顺序排序 + if (p0.x === p1.x) { + jointArray = jointArray.sort(function (a, b) { + return a.y - b.y; + }); + } + else { + jointArray = jointArray.sort(function (a, b) { + return a.x - b.x; + }); + } + //console.log(jointArray); - // // 轮廓为凹的需要寻找可以合并的轮廓 - // var last = splitedPaths.length - 1; - // splitedPaths[last].direction = util.isClockWise(splitedPaths[last]); - // for (var i = last - 1; i > 0; i--) { - // splitedPaths[i].direction = util.isClockWise(splitedPaths[i]); - // if (splitedPaths[i].direction !== splitedPaths[i + 1].direction) { - // var prevPrev = i === last - 1 ? 0 : i + 2; - // var newPath = splitedPaths[prevPrev].concat(splitedPaths[i]); - // newPath.direction = splitedPaths[i + 1].direction; + // 按照 no-zero 规则获取交点线段 + var start = jointArray[0]; + var zCount = start.zCount; + var jointSegment = []; + for (i = 1, l = jointArray.length; i < l; i++) { + zCount += jointArray[i].zCount; + if (!zCount) { + jointSegment.push([start, jointArray[i]]); + start = jointArray[++i]; + zCount = start ? start.zCount : 0; + } + } - // if (prevPrev === 0) { - // splitedPaths[0] = newPath; - // splitedPaths.splice(i, 1); - // } - // else { - // splitedPaths[i] = newPath; - // splitedPaths.splice(prevPrev, 1); - // } - // } - // } + // TODO 组合左右两侧的曲线段 + console.log(jointSegment); + console.log(leftSide); + console.log(rightSide); return paths.map(function (path) { diff --git a/src/graphics/pathUtil.js b/src/graphics/pathUtil.js index 5df4193..40cf175 100644 --- a/src/graphics/pathUtil.js +++ b/src/graphics/pathUtil.js @@ -10,6 +10,7 @@ define( function (require) { + var getPointHash = require('./util').getPointHash; /** * 对路径进行插值,补全省略的点 @@ -98,16 +99,6 @@ define( : zCount < 0 ? 1 : -1; } - /** - * 获取点的hash - * @param {Object} p 点对象 - * - * @return {number} 哈希值 - */ - function getPointHash(p) { - return Math.floor(7 * Math.floor(p.x * 100) + 31 * Math.floor(p.y * 100)) + (p.onCurve ? 1 : 0); - } - /** * 获取路径哈希 * @param {Array} path 路径数组 @@ -119,7 +110,7 @@ define( var seed = 131; path.forEach(function (p) { - hash = 0x7FFFFFFF & (hash * seed + getPointHash(p)); + hash = 0x7FFFFFFF & (hash * seed + getPointHash(p) + (p.onCurve ? 1 : 0)); }); return hash; @@ -150,7 +141,6 @@ define( deInterpolate: deInterpolate, isClockWise: isClockWise, removeOverlapPoints: removeOverlapPoints, - getPointHash: getPointHash, getPathHash: getPathHash }; } diff --git a/src/graphics/util.js b/src/graphics/util.js index 0e1da4f..2644942 100644 --- a/src/graphics/util.js +++ b/src/graphics/util.js @@ -85,11 +85,24 @@ define( return ceil(p0.x) === ceil(p1.x) && ceil(p0.y) === ceil(p1.y); } + + /** + * 获取点的hash值 + * + * @param {Object} p p + * @param {Object} p1 p1 + * @return {number} + */ + function getPointHash(p) { + return Math.floor(7 * Math.floor(p.x * 100) + 31 * Math.floor(p.y * 100)); + } + return { ceil: ceil, ceilPoint: ceilPoint, isPointInBound: isPointInBound, - isPointOverlap: isPointOverlap + isPointOverlap: isPointOverlap, + getPointHash: getPointHash }; } );