From 4637e9a3bf824301b468e14b42c50ab92b062044 Mon Sep 17 00:00:00 2001 From: mkwiser Date: Tue, 28 Oct 2014 02:19:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20=E5=A4=9A=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E6=B1=82=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- demo/js/contours-1.js | 2 +- demo/js/contours-3.js | 2 +- demo/js/contours-4.js | 6 + demo/js/contours-5.js | 6 + demo/js/contours-6.js | 6 + demo/js/contours-7.js | 6 + demo/js/contoursCombine.js | 69 +++++---- src/editor/command/shapes.js | 103 ++++++++++++++ src/editor/command/support.js | 1 + src/editor/menu/commandList.js | 11 ++ src/editor/mode/shapes.js | 20 ++- src/editor/shapes/circle.js | 11 +- src/graphics/isBezierRayCross.js | 2 +- src/graphics/isPathCross.js | 21 +++ src/graphics/pathJoin.js | 235 ++++++++++++++++++++++++------- src/math/bezierQ2Split.js | 13 +- src/math/getBezierQ2Point.js | 31 ++++ src/math/getBezierQ2T.js | 11 +- 19 files changed, 447 insertions(+), 113 deletions(-) create mode 100644 demo/js/contours-4.js create mode 100644 demo/js/contours-5.js create mode 100644 demo/js/contours-6.js create mode 100644 demo/js/contours-7.js create mode 100644 src/editor/command/shapes.js create mode 100644 src/math/getBezierQ2Point.js diff --git a/README.md b/README.md index 9d895c0..9d34853 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,6 @@ fonteditor 在线ttf字体编辑器 1. 2014-10-7 ttf管理器发布,[试玩版地址](http://mkwiser.sinaapp.com/fonteditor/ttf.html). -2. 2014-10-19 ttf编辑器发布,[试玩版地址](http://mkwiser.sinaapp.com/fonteditor/index.html). \ No newline at end of file +2. 2014-10-19 ttf编辑器发布,[试玩版地址](http://mkwiser.sinaapp.com/fonteditor/index.html). + +3. 2014-10-28 ttf编辑器增加路径合集、交集、差集,[试玩版地址](http://mkwiser.sinaapp.com/fonteditor/index.html). \ No newline at end of file diff --git a/demo/js/contours-1.js b/demo/js/contours-1.js index 0c1475b..2e1ed51 100644 --- a/demo/js/contours-1.js +++ b/demo/js/contours-1.js @@ -1,6 +1,6 @@ define( function(require) { - return {"xMin":26,"yMin":-10,"xMax":601,"yMax":212,"unicode":[57357],"advanceWidth":633,"leftSideBearing":26,"name":"uniE00D","contours":[[{"x":230,"y":159},{"x":230,"y":84},{"x":283,"y":31},{"x":357,"y":31},{"x":410,"y":84},{"x":410,"y":159},{"x":357,"y":212},{"x":283,"y":212}],[{"x":597,"y":61,"onCurve":true},{"x":597,"y":62},{"x":595,"y":63},{"x":594,"y":64,"onCurve":true},{"x":586,"y":78},{"x":572,"y":84,"onCurve":true},{"x":459,"y":177},{"x":312,"y":177,"onCurve":true},{"x":163,"y":177},{"x":49,"y":81,"onCurve":true},{"x":42,"y":76},{"x":37,"y":70,"onCurve":true},{"x":36,"y":69},{"x":33,"y":66},{"x":33,"y":65,"onCurve":true},{"x":26,"y":53},{"x":26,"y":39,"onCurve":true},{"x":26,"y":19},{"x":55,"y":-10},{"x":75,"y":-10,"onCurve":true},{"x":91,"y":-10},{"x":105,"y":0,"onCurve":true},{"x":193,"y":80},{"x":312,"y":80,"onCurve":true},{"x":435,"y":80},{"x":524,"y":-4,"onCurve":true},{"x":526,"y":-2,"onCurve":true},{"x":538,"y":-10},{"x":552,"y":-10,"onCurve":true},{"x":573,"y":-10},{"x":601,"y":19},{"x":601,"y":39,"onCurve":true},{"x":601,"y":50},{"x":596,"y":60,"onCurve":true}]]}; + return {"xMin":37,"yMin":78,"xMax":439,"yMax":390,"unicode":[57357],"advanceWidth":633,"leftSideBearing":37,"name":"uniE00D","contours":[[{"x":74,"y":289,"onCurve":true},{"x":98,"y":277},{"x":128,"y":277,"onCurve":true},{"x":160,"y":277},{"x":186,"y":292,"onCurve":true},{"x":225,"y":254},{"x":281,"y":254,"onCurve":true},{"x":337,"y":254},{"x":377,"y":292,"onCurve":true},{"x":416,"y":329},{"x":416,"y":383,"onCurve":true},{"x":416,"y":387},{"x":416,"y":390,"onCurve":true},{"x":428,"y":390,"onCurve":true},{"x":428,"y":78,"onCurve":true},{"x":74,"y":78,"onCurve":true}],[{"x":439,"y":210,"onCurve":true},{"x":437,"y":259},{"x":400,"y":295,"onCurve":true},{"x":360,"y":332},{"x":304,"y":332,"onCurve":true},{"x":250,"y":332},{"x":212,"y":298,"onCurve":true},{"x":185,"y":314},{"x":151,"y":314,"onCurve":true},{"x":103,"y":314},{"x":37,"y":251},{"x":37,"y":160},{"x":70,"y":129,"onCurve":true},{"x":83,"y":117},{"x":97,"y":109,"onCurve":true},{"x":97,"y":210,"onCurve":true}]]} } ); diff --git a/demo/js/contours-3.js b/demo/js/contours-3.js index 50180f7..4921261 100644 --- a/demo/js/contours-3.js +++ b/demo/js/contours-3.js @@ -1,6 +1,6 @@ define( function(require) { - return {"xMin":0,"yMin":-32,"xMax":512,"yMax":480,"unicode":[57358],"advanceWidth":512,"leftSideBearing":0,"name":"uniE00E","contours":[[{"x":362,"y":480},{"x":150,"y":480},{"x":0,"y":330},{"x":0,"y":118},{"x":150,"y":-32},{"x":362,"y":-32},{"x":512,"y":118},{"x":512,"y":330}],[{"x":360,"y":148,"onCurve":true},{"x":365,"y":143},{"x":365,"y":127},{"x":360,"y":122,"onCurve":true},{"x":351,"y":113,"onCurve":true},{"x":345,"y":108},{"x":330,"y":108},{"x":325,"y":113,"onCurve":true},{"x":254,"y":184,"onCurve":true},{"x":183,"y":114,"onCurve":true},{"x":178,"y":108},{"x":163,"y":108},{"x":157,"y":114,"onCurve":true},{"x":148,"y":122,"onCurve":true},{"x":143,"y":128},{"x":143,"y":143},{"x":148,"y":148,"onCurve":true},{"x":219,"y":219,"onCurve":true},{"x":148,"y":290,"onCurve":true},{"x":143,"y":295},{"x":143,"y":311},{"x":148,"y":316,"onCurve":true},{"x":157,"y":325,"onCurve":true},{"x":162,"y":330},{"x":177,"y":330},{"x":183,"y":325,"onCurve":true},{"x":254,"y":254,"onCurve":true},{"x":325,"y":325,"onCurve":true},{"x":330,"y":330},{"x":346,"y":330},{"x":351,"y":325,"onCurve":true},{"x":360,"y":316,"onCurve":true},{"x":365,"y":311},{"x":365,"y":295},{"x":360,"y":290,"onCurve":true},{"x":289,"y":219,"onCurve":true}]]}; + return {"xMin":58,"yMin":142,"xMax":504,"yMax":446,"unicode":[57357],"advanceWidth":633,"leftSideBearing":58,"name":"uniE00D","contours":[[{"x":404.20977,"y":306,"onCurve":true},{"x":587,"y":306,"onCurve":true},{"x":587,"y":580,"onCurve":true},{"x":314,"y":580,"onCurve":true},{"x":314,"y":353.71808,"onCurve":true},{"x":323.41730917023847,"y":355},{"x":334,"y":355,"onCurve":true},{"x":364,"y":355},{"x":386,"y":344,"onCurve":true},{"x":407,"y":333},{"x":407,"y":317,"onCurve":true},{"x":407,"y":311.1678292421482}],[{"x":659,"y":337},{"x":683,"y":328,"onCurve":true},{"x":707,"y":319},{"x":707,"y":306,"onCurve":true},{"x":707,"y":294},{"x":683,"y":285,"onCurve":true},{"x":659,"y":276},{"x":625,"y":276,"onCurve":true},{"x":591,"y":276},{"x":568,"y":285,"onCurve":true},{"x":545,"y":294},{"x":545,"y":306,"onCurve":true},{"x":545,"y":319},{"x":568,"y":328,"onCurve":true},{"x":591,"y":337},{"x":625,"y":337,"onCurve":true}]]}; } ); diff --git a/demo/js/contours-4.js b/demo/js/contours-4.js new file mode 100644 index 0000000..04a04fd --- /dev/null +++ b/demo/js/contours-4.js @@ -0,0 +1,6 @@ + +define( + function(require) { + return {"xMin":63,"yMin":176,"xMax":278,"yMax":406,"unicode":[57357],"advanceWidth":633,"leftSideBearing":63,"name":"uniE00D","contours":[[{"x":198,"y":176},{"x":214,"y":243},{"x":187,"y":337},{"x":131,"y":404},{"x":79,"y":404},{"x":63,"y":337},{"x":90,"y":243},{"x":146,"y":176}],[{"x":197,"y":177},{"x":247,"y":244},{"x":278,"y":339},{"x":272,"y":406},{"x":231,"y":406},{"x":181,"y":339},{"x":150,"y":244},{"x":156,"y":177}]]} + } +); diff --git a/demo/js/contours-5.js b/demo/js/contours-5.js new file mode 100644 index 0000000..502e718 --- /dev/null +++ b/demo/js/contours-5.js @@ -0,0 +1,6 @@ + +define( + function(require) { + return {"xMin":114,"yMin":-6,"xMax":486,"yMax":430,"unicode":[57357],"advanceWidth":633,"leftSideBearing":114,"name":"uniE00D","contours":[[{"x":226,"y":255},{"x":272,"y":301},{"x":272,"y":367},{"x":226,"y":413},{"x":160,"y":413},{"x":114,"y":367},{"x":114,"y":301},{"x":160,"y":255}],[{"x":431,"y":242},{"x":486,"y":297},{"x":486,"y":375},{"x":431,"y":430},{"x":353,"y":430},{"x":298,"y":375},{"x":298,"y":297},{"x":353,"y":242}],[{"x":326,"y":-6},{"x":380,"y":48},{"x":380,"y":124},{"x":326,"y":178},{"x":249,"y":178},{"x":196,"y":124},{"x":196,"y":48},{"x":249,"y":-6}],[{"x":178,"y":340,"onCurve":true},{"x":423,"y":340,"onCurve":true},{"x":423,"y":113,"onCurve":true},{"x":178,"y":113,"onCurve":true}]]} + } +); diff --git a/demo/js/contours-6.js b/demo/js/contours-6.js new file mode 100644 index 0000000..479709d --- /dev/null +++ b/demo/js/contours-6.js @@ -0,0 +1,6 @@ + +define( + function(require) { + return {"xMin":55,"yMin":-7,"xMax":494,"yMax":430,"unicode":[57357],"advanceWidth":633,"leftSideBearing":55,"name":"uniE00D","contours":[[{"x":319,"y":259},{"x":342,"y":282,"onCurve":true},{"x":365,"y":305},{"x":365,"y":338,"onCurve":true},{"x":365,"y":371},{"x":342,"y":394,"onCurve":true},{"x":319,"y":417},{"x":286,"y":417,"onCurve":true},{"x":253,"y":417},{"x":230,"y":394,"onCurve":true},{"x":207,"y":371},{"x":207,"y":338,"onCurve":true},{"x":207,"y":305},{"x":230,"y":282,"onCurve":true},{"x":253,"y":259},{"x":286,"y":259,"onCurve":true}],[{"x":431,"y":242},{"x":459,"y":270,"onCurve":true},{"x":486,"y":297},{"x":486,"y":336,"onCurve":true},{"x":486,"y":375},{"x":459,"y":403,"onCurve":true},{"x":431,"y":430},{"x":392,"y":430,"onCurve":true},{"x":353,"y":430},{"x":326,"y":403,"onCurve":true},{"x":298,"y":375},{"x":298,"y":336,"onCurve":true},{"x":298,"y":297},{"x":326,"y":270,"onCurve":true},{"x":353,"y":242},{"x":392,"y":242,"onCurve":true}],[{"x":185,"y":6},{"x":212,"y":33,"onCurve":true},{"x":239,"y":60},{"x":239,"y":98,"onCurve":true},{"x":239,"y":136},{"x":212,"y":163,"onCurve":true},{"x":185,"y":190},{"x":147,"y":190,"onCurve":true},{"x":108,"y":190},{"x":82,"y":163,"onCurve":true},{"x":55,"y":136},{"x":55,"y":98,"onCurve":true},{"x":55,"y":60},{"x":82,"y":33,"onCurve":true},{"x":108,"y":6},{"x":147,"y":6,"onCurve":true}],[{"x":178,"y":340,"onCurve":true},{"x":423,"y":340,"onCurve":true},{"x":423,"y":113,"onCurve":true},{"x":178,"y":113,"onCurve":true}],[{"x":435,"y":-7},{"x":465,"y":23,"onCurve":true},{"x":494,"y":52},{"x":494,"y":94,"onCurve":true},{"x":494,"y":135},{"x":465,"y":165,"onCurve":true},{"x":435,"y":194},{"x":393,"y":194,"onCurve":true},{"x":351,"y":194},{"x":322,"y":165,"onCurve":true},{"x":293,"y":135},{"x":293,"y":94,"onCurve":true},{"x":293,"y":52},{"x":322,"y":23,"onCurve":true},{"x":351,"y":-7},{"x":393,"y":-7,"onCurve":true}]]} + } +); diff --git a/demo/js/contours-7.js b/demo/js/contours-7.js new file mode 100644 index 0000000..d6fd007 --- /dev/null +++ b/demo/js/contours-7.js @@ -0,0 +1,6 @@ + +define( + function(require) { + return {"xMin":63,"yMin":176,"xMax":214,"yMax":407,"unicode":[57357],"advanceWidth":633,"leftSideBearing":63,"name":"uniE00D","contours":[[{"x":198,"y":176},{"x":206,"y":210,"onCurve":true},{"x":214,"y":243},{"x":201,"y":290,"onCurve":true},{"x":187,"y":337},{"x":159,"y":371,"onCurve":true},{"x":131,"y":404},{"x":105,"y":404,"onCurve":true},{"x":79,"y":404},{"x":71,"y":371,"onCurve":true},{"x":63,"y":337},{"x":77,"y":290,"onCurve":true},{"x":90,"y":243},{"x":118,"y":210,"onCurve":true},{"x":146,"y":176},{"x":172,"y":176,"onCurve":true}],[{"x":128,"y":178},{"x":153,"y":212,"onCurve":true},{"x":178,"y":245},{"x":194,"y":293,"onCurve":true},{"x":209,"y":340},{"x":206,"y":374,"onCurve":true},{"x":203,"y":407},{"x":183,"y":407,"onCurve":true},{"x":162,"y":407},{"x":137,"y":374,"onCurve":true},{"x":112,"y":340},{"x":97,"y":293,"onCurve":true},{"x":81,"y":245},{"x":84,"y":212,"onCurve":true},{"x":87,"y":178},{"x":108,"y":178,"onCurve":true}]]}; + } +); diff --git a/demo/js/contoursCombine.js b/demo/js/contoursCombine.js index 77c7539..3dd9e9d 100644 --- a/demo/js/contoursCombine.js +++ b/demo/js/contoursCombine.js @@ -11,7 +11,7 @@ define( var lang = require('common/lang'); var editor = require('editor/main'); - var shape_baidu = require('./contours-3'); + var shape_baidu = require('./contours-1'); var isPathCross = require('graphics/isPathCross'); var pathJoin = require('graphics/pathJoin'); var util = require('graphics/util'); @@ -27,52 +27,51 @@ define( var clonedShape = lang.clone(shape_baidu); //clonedShape.contours[1].reverse(); - // 插值 - clonedShape.contours[0] = util.interpolate(clonedShape.contours[0]); - clonedShape.contours[1] = util.interpolate(clonedShape.contours[1]); - currentEditor = editor.create($('#render-view').get(0)); + window.editor = currentEditor = editor.create($('#render-view').get(0)); currentEditor.setFont(clonedShape); - var jointLayer = currentEditor.fontLayer; + // var jointLayer = currentEditor.fontLayer; - var path0 = currentEditor.fontLayer.shapes[0].points; - var path1 = currentEditor.fontLayer.shapes[1].points; - var result = isPathCross(path0, path1); + // var paths = currentEditor.fontLayer.shapes.map(function(shape) { + // return shape.points; + // }); + + // // var result = isPathCross(path0, path1); - if (result && result.length) { - result.forEach(function(p, index){ - jointLayer.addShape({ - type: 'point', - x: p.x, - y: p.y, - style: { - fill: true, - fillColor: 'red' - } - }); - }); + // // if (result && result.length) { + // // result.forEach(function(p, index){ + // // jointLayer.addShape({ + // // type: 'point', + // // x: p.x, + // // y: p.y, + // // style: { + // // fill: true, + // // fillColor: 'red' + // // } + // // }); + // // }); - jointLayer.refresh(); - } + // // jointLayer.refresh(); + // // } - var paths = pathJoin(path0, path1, 4); + // var newPaths = pathJoin(paths, 4); - paths.forEach(function(p, index) { - jointLayer.addShape('path', { - points: lang.clone(p), - style: { - lineWidth: 2, - fill:true, - fillColor: index % 2 ? 'red' : 'blue' - } - }); - }); + // newPaths.forEach(function(p, index) { + // jointLayer.addShape('path', { + // points: lang.clone(p), + // style: { + // lineWidth: 2, + // fill:true, + // fillColor: index % 2 ? 'red' : 'blue' + // } + // }); + // }); - jointLayer.refresh(); + // jointLayer.refresh(); } }; diff --git a/src/editor/command/shapes.js b/src/editor/command/shapes.js new file mode 100644 index 0000000..b5e0494 --- /dev/null +++ b/src/editor/command/shapes.js @@ -0,0 +1,103 @@ +/** + * @file shapes.js + * @author mengke01 + * @date + * @description + * 轮廓合并操作 + */ + + +define( + function(require) { + + var pathJoin = require('graphics/pathJoin'); + var lang = require('common/lang'); + + // shape的组合操作 + function combineShape(shapes, relation) { + + var pathList = shapes.map(function(path) { + return path.points; + }); + + var result = pathJoin(pathList, relation); + + // 检查有shapes没有改变过 + var changed = false; + if (result.length === pathList.length) { + for (var i = 0, l = result.length; i < l; i ++ ) { + if (result[i] !== pathList[i]) { + changed = true; + break; + } + } + } + else { + changed = true; + } + + // 有改变则更新节点集合 + if (changed) { + + var fontLayer = this.fontLayer; + var resultLength = result.length; + var shapesLength = shapes.length; + var length = Math.min(resultLength, shapesLength); + + // 替换原来位置的 + for (var i = 0; i < length; i++) { + shapes[i].points = lang.clone(result[i]); + } + + // 移除多余的 + if (shapesLength > length) { + for (var i = length; i < shapesLength; i++) { + fontLayer.removeShape(shapes[i]); + } + shapes.splice(length, shapesLength - length); + } + + // 添加新的shape + if (resultLength > length) { + for (var i = length; i < resultLength; i++) { + var shape = fontLayer.addShape('path', { + points: result[i] + }); + shapes.push(shape); + } + } + + fontLayer.refresh(); + } + + return shapes; + } + + + var support = { + + /** + * 结合 + */ + joinshapes: function(shapes) { + combineShape.call(this, shapes, pathJoin.JOIN); + }, + + /** + * 相交 + */ + intersectshapes: function(shapes) { + combineShape.call(this, shapes, pathJoin.INTERSECT); + }, + + /** + * 相切 + */ + tangencyshapes: function(shapes) { + combineShape.call(this, shapes, pathJoin.TANGENCY); + } + }; + + return support; + } +); diff --git a/src/editor/command/support.js b/src/editor/command/support.js index d7296f8..f56760e 100644 --- a/src/editor/command/support.js +++ b/src/editor/command/support.js @@ -261,6 +261,7 @@ define( }; lang.extend(support, require('./transform')); + lang.extend(support, require('./shapes')); return support; } diff --git a/src/editor/menu/commandList.js b/src/editor/menu/commandList.js index 1d8a60f..ecb6bbc 100644 --- a/src/editor/menu/commandList.js +++ b/src/editor/menu/commandList.js @@ -107,6 +107,17 @@ define( title: '复制' }, + 'join_shapes': { + title: '结合' + }, + + 'intersect_shapes': { + title: '相交' + }, + + 'tangency_shapes': { + title: '相切' + }, 'rotate_left': { title: '向左旋转' diff --git a/src/editor/mode/shapes.js b/src/editor/mode/shapes.js index a47b5f9..dbe1e33 100644 --- a/src/editor/mode/shapes.js +++ b/src/editor/mode/shapes.js @@ -72,6 +72,24 @@ define( else if (command == 'down') { this.execCommand('downshape', shape); } + + // 相交,结合和相切 + else if (command == 'join_shapes') { + this.execCommand('joinshapes', shapes); + this.currentGroup.setShapes(shapes); + this.currentGroup.refresh(); + } + else if (command == 'intersect_shapes') { + this.execCommand('intersectshapes', shapes); + this.currentGroup.setShapes(shapes); + this.currentGroup.refresh(); + } + else if (command == 'tangency_shapes') { + this.execCommand('tangencyshapes', shapes); + this.currentGroup.setShapes(shapes); + this.currentGroup.refresh(); + } + else if (command == 'rotate_left') { this.execCommand('rotateleft', shapes); this.currentGroup.setShapes(shapes); @@ -141,7 +159,7 @@ define( var shapeIndex = this.currentGroup.shapes.indexOf(shape); if(shapeIndex >= 0) { - if (e.ctrlKey) { + if (e.ctrlKey && !e.altKey) { this.currentGroup.shapes.splice(shapeIndex, 1); this.currentGroup.setShapes(this.currentGroup.shapes); this.currentGroup.refresh(); diff --git a/src/editor/shapes/circle.js b/src/editor/shapes/circle.js index 955503d..fb1cb1b 100644 --- a/src/editor/shapes/circle.js +++ b/src/editor/shapes/circle.js @@ -13,16 +13,7 @@ define( yMin:0, xMax: 300, yMax: 300, - points:[ - {x:212, y: 300}, - {x:300, y: 212}, - {x:300, y: 88}, - {x:212, y: 0}, - {x:87, y: 0}, - {x:0, y: 88}, - {x:0, y: 212}, - {x:87, y: 300} - ] + points:[{"x":87,"y":300},{"x":0,"y":212},{"x":0,"y":88},{"x":87,"y":0},{"x":212,"y":0},{"x":300,"y":88},{"x":300,"y":212},{"x":212,"y":300}] }; } ); diff --git a/src/graphics/isBezierRayCross.js b/src/graphics/isBezierRayCross.js index 35fe31d..1755b75 100644 --- a/src/graphics/isBezierRayCross.js +++ b/src/graphics/isBezierRayCross.js @@ -18,7 +18,7 @@ define( * @return {Array|boolean} 交点数组或者false */ function isBezierRayCross(p0, p1, p2, p) { - + // 3点都在同一侧 if(0 === ((p0.y > p.y) + (p1.y > p.y) + (p2.y > p.y)) % 3) { return false; diff --git a/src/graphics/isPathCross.js b/src/graphics/isPathCross.js index 4085641..37f97ae 100644 --- a/src/graphics/isPathCross.js +++ b/src/graphics/isPathCross.js @@ -88,6 +88,10 @@ define( return joint.length ? joint : false; } + function hashcode(p) { + return p.x / 7 + p.y / 13 + (p.x + p.y) / 17; + } + /** * 判断x轴射线是否穿过线段 * @@ -117,6 +121,23 @@ define( } } else { + + // 对结果集合进行筛选,去除重复点 + var hash = {}; + for (var i = result.length - 1; i >= 0; i--) { + var p = result[i]; + if (hash[hashcode(p)]) { + result.splice(i, 1); + } + else { + hash[hashcode(p)] = true; + } + } + + if (result.length === 1) { + return false; + } + return result; } } diff --git a/src/graphics/pathJoin.js b/src/graphics/pathJoin.js index 681bc19..40b74ff 100644 --- a/src/graphics/pathJoin.js +++ b/src/graphics/pathJoin.js @@ -13,7 +13,9 @@ define( var isInsidePath = require('./isInsidePath'); var bezierQ2Split = require('math/bezierQ2Split'); var getBezierQ2T = require('math/getBezierQ2T'); + var getBezierQ2Point = require('math/getBezierQ2Point'); var util = require('./util'); + var splice = Array.prototype.splice; /** * 按索引号排序相交点,这里需要处理曲线段上有多个交点的问题 @@ -43,7 +45,7 @@ define( var prev = p0.index == 0 ? path[length - 1] : path[p0.index - 1]; var t1 = getBezierQ2T(prev, cur, next, p0); var t2 = getBezierQ2T(prev, cur, next, p1); - return t1 < t2 ? -1 : 1; + return t1 == t2 ? 0 : t1 < t2 ? -1 : 1; } } @@ -72,7 +74,6 @@ define( function splitPath(path, joint) { joint = sortJoint(path, joint); - var jointOffset = 0; // 用来标记插入点产生的偏移 // 插入分割点 @@ -99,54 +100,69 @@ define( else if (!path[cur].onCurve) { var prev = cur == 0 ? length - 1 : cur - 1; var bezierArray = bezierQ2Split(path[prev], path[cur], path[next], p); + + if (!bezierArray) { + throw 'can\'t split path'; + } - path.splice(cur, 1, - bezierArray[0][1], - { - x: p.x, - y: p.y, - onCurve: true - }, - bezierArray[1][1] - ); + // 端点情况 + if (bezierArray.length === 1) { + p.index = bezierArray[0] === 0 ? prev : next; + } + else { + path.splice(cur, 1, + bezierArray[0][1], + { + x: p.x, + y: p.y, + onCurve: true + }, + bezierArray[1][1] + ); - p.index = cur + 1; - jointOffset += 2; + p.index = cur + 1; + jointOffset += 2; + } } else { throw 'can\'t get here'; } } + // 这里需要重新筛选排序 + joint.sort(function(p0, p1) { + return p0.index - p1.index; + }); + // 分割曲线 - var splitPaths = []; + var splitedPaths = []; var start; var end; for (var i = 0, l = joint.length - 1; i < l; i++) { start = joint[i]; end = joint[i + 1]; - splitPaths.push(path.slice(start.index, end.index + 1)); + splitedPaths.push(path.slice(start.index, end.index + 1)); } // 闭合轮廓 start = end; end = joint[0]; - splitPaths.push(path.slice(start.index).concat(path.slice(0, end.index + 1))); - return splitPaths; + splitedPaths.push(path.slice(start.index).concat(path.slice(0, end.index + 1))); + return splitedPaths; } /** * 获取分割的路径hash * - * @param {Array} splitPath 分割的路径 + * @param {Array} splitedPath 分割的路径 * @return {Object} 哈希值 */ - function getSplitPathHash(splitPath) { + function getSplitedPathHash(splitedPath) { var splitHash = {}; var code; // 根据起始点创建hash - splitPath.forEach(function(path) { + splitedPath.forEach(function(path) { // 开始点 code = hashcode(path[0]); if (!splitHash[code]) { @@ -168,15 +184,15 @@ define( /** * 组合路径 * - * @param {Array} splitPaths0 分割后的路径1 - * @param {Array} splitPaths1 分割后的路径2 + * @param {Array} splitedPaths0 分割后的路径1 + * @param {Array} splitedPaths1 分割后的路径2 * @param {number} relation 分割关系 * @return {Array} 组合后的路径 */ - function combinePath(splitPaths0, splitPaths1, relation) { - + function combinePath(splitedPaths0, splitedPaths1, relation) { + var direction0 = splitedPaths0.direction; + var direction1 = splitedPaths1.direction; var newPaths = []; - var splice = Array.prototype.splice; // 过滤路径 var filterPath = function(path){ @@ -191,32 +207,38 @@ define( } }; - splitPaths0 = splitPaths0.filter(filterPath); - splitPaths1 = splitPaths1.filter(filterPath); + splitedPaths0 = splitedPaths0.filter(filterPath); + splitedPaths1 = splitedPaths1.filter(filterPath); // 计算哈希,用来辅助组合点 - var splitHash0 = getSplitPathHash(splitPaths0); - var splitHash1 = getSplitPathHash(splitPaths1); + var splitHash0 = getSplitedPathHash(splitedPaths0); + var splitHash1 = getSplitedPathHash(splitedPaths1); - for (var i = 0; i < splitPaths0.length; i++) { - var length = splitPaths0[i].length; - var newPath = splitPaths0[i].slice(0, length - 1); - - var start = splitPaths0[i][0]; - var end = splitPaths0[i][length - 1]; + for (var i = 0; i < splitedPaths0.length; i++) { + var curPath = splitedPaths0[i]; + var cross = curPath.cross; // 起始cross + var length = curPath.length; + + // 对于求相切的情况,外轮廓不需要处理,内轮廓需要根据另一个轮廓的方向调整反向 + if (relation == pathJoin.TANGENCY && cross && direction0 == direction1) { + curPath = curPath.reverse(); + } + + var newPath = curPath.slice(0, length - 1); + var start = curPath[0]; + var end = curPath[length - 1]; var nextHash = splitHash1; - var cross = splitPaths0[i].cross; // 起始cross var loops = 0; // 防止死循环,最多组合100个路径段 - while (loops++ < 100 && (Math.abs(start.x - end.x) > 0.0001 || Math.abs(start.y - end.y) > 0.0001)) { + while (++loops < 100 && (Math.abs(start.x - end.x) > 0.001 || Math.abs(start.y - end.y) > 0.001)) { var paths = nextHash[hashcode(end)]; // 选取异向 var p = paths[0]; - if (relation == pathJoin.TANGENCY && cross == p.cross) { + if (relation == pathJoin.TANGENCY && cross == p.cross && paths.length > 1) { p = paths[1]; } @@ -236,14 +258,16 @@ define( } else { nextHash = splitHash1; - // 这里需要去掉已经使用的splitPaths0上的点 - splitPaths0.splice(splitPaths0.indexOf(p), 1); + // 这里需要去掉已经使用的splitedPaths0上的点 + splitedPaths0.splice(splitedPaths0.indexOf(p), 1); } } newPaths.push(newPath); } - + if (loops >= 100) { + throw '没有找到可组合的轮廓!'; + } if (newPaths.length) { return newPaths.map(function(path){ @@ -265,7 +289,7 @@ define( * @param {number} relation 关系 * @return {Array} 新的路径集合 */ - function pathJoin(path0, path1, relation) { + function join2Path(path0, path1, relation) { // 方向, 0 顺时针, 1 逆时针 var direction0 = util.isClockWise(path0); var direction1 = util.isClockWise(path1); @@ -277,28 +301,49 @@ define( // 获取组合后的路径 var getJoinPaths = function(joint) { - var splitPaths0 = splitPath(newPath0, joint.map(function(p) { + var splitedPaths0 = splitPath(newPath0, joint.map(function(p) { p.index = p.index0; return p; })); // 求路径是否在另一个路径内 - var inPath = isInsidePath(path1, splitPaths0[0][1]); - splitPaths0 = splitPaths0.map(function(path) { - path.direction = direction0; - path.cross = inPath; - inPath = !inPath; - return path; + var inPath = false; + splitedPaths0 = splitedPaths0.map(function(splitedPath) { + splitedPath.cross = isInsidePath( + path1, + splitedPath[1].onCurve + ? splitedPath[1] + : getBezierQ2Point(splitedPath[0], splitedPath[1], splitedPath[2], 0.5) + ); + if (splitedPath.cross) { + inPath = true; + } + return splitedPath; }); - var splitPaths1 = splitPath(newPath1, joint.map(function(p) { + // 只有相交的点,没有相交的轮廓 + if (!inPath) { + if (relation == pathJoin.JOIN || relation == pathJoin.TANGENCY) { + return [path0, path1]; + } + else if (relation == pathJoin.INTERSECT) { + return []; + } + } + + var splitedPaths1 = splitPath(newPath1, joint.map(function(p) { p.index = p.index1; return p; })); - inPath = isInsidePath(path0, splitPaths1[0][1]); - splitPaths1 = splitPaths1.map(function(path) { - path.direction = direction1; + var splitedPath = splitedPaths1[0]; + inPath = isInsidePath( + path0, + splitedPath[1].onCurve + ? splitedPath[1] + : getBezierQ2Point(splitedPath[0], splitedPath[1], splitedPath[2], 0.5) + ); + splitedPaths1 = splitedPaths1.map(function(path) { path.cross = inPath; inPath = !inPath; return path; @@ -313,7 +358,10 @@ define( return []; } - return combinePath(splitPaths0, splitPaths1, relation); + splitedPaths0.direction = direction0; + splitedPaths1.direction = direction1; + + return combinePath(splitedPaths0, splitedPaths1, relation); }; @@ -373,6 +421,85 @@ define( return [path0, path1]; } + + /** + * 求路径交集、并集、差集 + * + * @param {Array} paths 路径集合 + * @param {number} relation 关系 + * @return {Array} 合并后的路径 + */ + function pathJoin(paths, relation) { + if (paths.length == 1) { + if (relation == pathJoin.INTERSECT) { + return []; + } + else { + return paths; + } + } + else if (paths.length == 2) { + return join2Path(paths[0], paths[1], relation); + } + else { + + // 算法描述: + // 1. 第一个路径为已选集合,后面依次为待选集合 + // 2. 从已选集合中和待选集合中各取一个轮廓求pathJoin + // 3. 如果有合并项目,则除去已选集合和待选集合中的路径,把新路径加入待选集合,继续2 + // 否则将待选集合中的当前路径加入已选集合,继续2 + // 4. 继续2、3步骤直到待选集合为空 + + // 已选集合 + var startPaths = [paths[0]]; + var leftPath = paths.slice(1); + var curPath; + var joinFlag = 0; // 两个轮廓如果是由同一个合并生成的,则不需要进行比较了 + + while (curPath = leftPath.shift()) { + for (var i = 0, l = startPaths.length; i < l; i++) { + + if (curPath.joinFlag && startPaths[i].joinFlag && (curPath.joinFlag & startPaths[i].joinFlag)) { + continue; + } + + var result = join2Path(curPath, startPaths[i], relation); + + // 相交关系,有个无交集则返回空 + if (relation == pathJoin.INTERSECT && !result.length) { + return []; + } + + // 原样返回,继续比较 + if (result.length == 2 && result[0] === curPath && result[1] === startPaths[i]) { + continue; + } + // 否则分割出来的点,加入待选集合 + else { + startPaths.splice(i, 1); + joinFlag ++; + result.forEach(function(path) { + path.joinFlag = (path.joinFlag || 0) + (1 << joinFlag); + leftPath.push(path); + }); + break; + } + } + + // 没有找到 + if (i == l) { + startPaths.push(curPath); + } + else if (!startPaths.length) { + startPaths.push(leftPath.shift()); + } + } + + return startPaths; + } + } + + pathJoin.JOIN = 1; // 并集 pathJoin.INTERSECT = 2; // 交集 pathJoin.TANGENCY = 4; // 相切 diff --git a/src/math/bezierQ2Split.js b/src/math/bezierQ2Split.js index 9129619..938ab6d 100644 --- a/src/math/bezierQ2Split.js +++ b/src/math/bezierQ2Split.js @@ -11,14 +11,7 @@ define( function(require) { var getBezierQ2T = require('./getBezierQ2T'); - - // 获取贝塞尔曲线上的点 - function getPoint(p0, p1, p2, t) { - return { - x: p0.x * Math.pow(1 - t, 2) + 2 * p1.x * t * (1-t) + p2.x * Math.pow(t, 2), - y: p0.y * Math.pow(1 - t, 2) + 2 * p1.y * t * (1-t) + p2.y * Math.pow(t, 2) - }; - } + var getPoint = require('./getBezierQ2Point'); /** * 分割贝塞尔曲线 @@ -45,6 +38,10 @@ define( } } + if (t == 0 || t == 1) { + return [t]; + } + return [ [ p0, diff --git a/src/math/getBezierQ2Point.js b/src/math/getBezierQ2Point.js new file mode 100644 index 0000000..189a94d --- /dev/null +++ b/src/math/getBezierQ2Point.js @@ -0,0 +1,31 @@ +/** + * @file getBezierQ2Point.js + * @author mengke01 + * @date + * @description + * 获取贝塞尔曲线上的点 + */ + + +define( + function(require) { + + /** + * 获取贝塞尔曲线上的点 + * + * @param {Object} p0 p0 + * @param {Object} p1 p1 + * @param {Object} p2 p2 + * @param {number} t + * @return {Object} 点 + */ + function getPoint(p0, p1, p2, t) { + return { + x: p0.x * Math.pow(1 - t, 2) + 2 * p1.x * t * (1-t) + p2.x * Math.pow(t, 2), + y: p0.y * Math.pow(1 - t, 2) + 2 * p1.y * t * (1-t) + p2.y * Math.pow(t, 2) + } + } + + return getPoint; + } +); diff --git a/src/math/getBezierQ2T.js b/src/math/getBezierQ2T.js index 5bdc8fb..984ec06 100644 --- a/src/math/getBezierQ2T.js +++ b/src/math/getBezierQ2T.js @@ -10,6 +10,7 @@ define( function(require) { var bezierQ2Equation = require('math/bezierQ2Equation'); + var getPoint = require('./getBezierQ2Point'); /** * 分割贝塞尔曲线 @@ -22,6 +23,14 @@ define( */ function getBezierQ2T(p0, p1, p2, p) { + // 极端情况 + if (Math.abs(p.x - p0.x) < 0.001 && Math.abs(p.y - p0.y) < 0.001) { + return 0; + } + else if (Math.abs(p.x - p2.x) < 0.001 && Math.abs(p.y - p2.y) < 0.001) { + return 1; + } + var result = bezierQ2Equation( p0.x + p2.x - 2 * p1.x + p0.y + p2.y - 2 * p1.y, 2 * (p1.x - p0.x) + 2 * (p1.y - p0.y), @@ -39,7 +48,7 @@ define( } else { var pt = getPoint(p0, p1, p2, result[0]); - if (Math.abs(pt.x - p.x) < 0.01 && Math.abs(pt.y - p.y) < 0.001) { + if (Math.abs(pt.x - p.x) < 0.001 && Math.abs(pt.y - p.y) < 0.001) { t = result[0]; } else {