js多边形(不规则的形状)之合并

  • js多边形(不规则的形状)之合并已关闭评论
  • 201 次浏览
  • A+
所属分类:Web前端
摘要

  注意: Polygon 目前并不完美 parameter:  提取地址: https://pan.baidu.com/s/1TV1j5BeZ7ZhidCq7aQXePA

 

js多边形(不规则的形状)之合并js多边形(不规则的形状)之合并

  1 /* Polygon 多边形   2    3 parameter:    4     path: Array[x, y];   5    6 attribute:   7    8     //只读属性   9     path: Array[x, y];   10   11 method:  12     add(x, y): this;             //x,y添加至path;  13     containsPoint(x, y): Bool;    //x,y是否在多边形的内部(注意: 在路径上也返回 true)  14     merge(polygon): this;         //合并; 假设polygon与this存在重叠的部分  15       16 */  17 class Polygon{  18   19     #position = null;  20     #path2D = null;  21   22     get path(){  23           24         return this.#position;  25   26     }  27   28     constructor(path = []){  29         this.#position = path;  30   31         this.#path2D = new Path2D();  32           33         var len = path.length;  34         if(len >= 2){  35             if(len % 2 !== 0){  36                 len -= 1;  37                 path.splice(len, 1);  38             }  39   40             const con = this.#path2D;  41             con.moveTo(path[0], path[1]);  42             for(let k = 2; k < len; k+=2) con.lineTo(path[k], path[k+1]);  43               44         }  45   46     }  47   48     add(x, y){  49         this.#position.push(x, y);  50         this.#path2D.lineTo(x, y);  51         return this;  52     }  53   54     containsPoint(x, y){  55           56         return UTILS.emptyContext.isPointInPath(this.#path2D, x, y);  57   58     }  59   60     toPoints(){  61         const path = this.path, len = path.length, result = [];  62           63         for(let k = 0; k < len; k += 2) result.push(new Point(path[k], path[k+1]));  64   65         return result;  66     }  67   68     toLines(){  69         const path = this.path, len = path.length, result = [];  70           71         for(let k = 0, x = NaN, y; k < len; k += 2){  72   73             if(isNaN(x)){  74                 x = path[k];  75                 y = path[k+1];  76                 continue;  77             }  78   79             const line = new Line(x, y, path[k], path[k+1]);  80               81             x = line.x1;  82             y = line.y1;  83   84             result.push(line);  85   86         }  87   88         return result;  89     }  90   91     merge(polygon){  92   93         const linesA = this.toLines(), linesB = polygon.toLines(), nodes = [], newLines = [];  94   95         //收集所有的交点 (保存至 line)  96         for(let k = 0, lenB = linesB.length, lenA = linesA.length, point = new Point(), pointB = new Point(); k < lenA; k++){  97             const lineA = linesA[k];  98             lineA.nodes = [];  99  100             for(let i = 0; i < lenB; i++){ 101                 const lineB = linesB[i]; 102                 if(lineB.nodes === undefined) lineB.nodes = []; 103                 if(lineA.intersectPoint(lineB, point) === point){ 104                     const node = { 105                         lineA: lineA,  106                         lineB: lineB,  107                         point: point.clone(), 108                         disA: point.distanceCompare(pointB.set(lineA.x, lineA.y)), 109                         disB: point.distanceCompare(pointB.set(lineB.x, lineB.y)), 110                     } 111                     lineA.nodes.push(node); 112                     lineB.nodes.push(node); 113                     nodes.push(node); 114                 } 115  116             } 117  118         } 119  120         //获取介入点 121         var startLine = null; 122         for(let k = 0, len = nodes.length, node; k < len; k++){ 123             node = nodes[k]; 124             if(node.lineA.nodes.length === 1 || node.lineA.nodes.length === 2 &&  125             node.lineB.nodes.length === 1 || node.lineB.nodes.length === 2){ 126                 startLine = node.lineA; 127                 break; 128             } 129         } 130  131         if(startLine === null){ 132             console.warn('Polygon: 找不到介入点, 终止了合并'); 133             return newLines; 134         } 135  136         //交点以原点为目标排序 137         for(let k = 0, sotr = function (a,b){return a.disA - b.disA;}, countA = linesA.length; k < countA; k++) linesA[k].nodes.sort(sotr); 138         for(let k = 0, sotr = function (a,b){return a.disB - b.disB;}, countB = linesB.length; k < countB; k++) linesB[k].nodes.sort(sotr); 139  140         const result_loop = { 141             lines: linesA, 142             loopType: 'next', 143             line: startLine, 144             count: startLine.nodes.length, 145             indexed: linesA.indexOf(startLine), 146         }, 147          148         //遍历某条线 149         loop = (lines, index, loopType) => { 150             const length = lines.length, indexed = index, model = lines === linesA ? polygon : this; 151  152             var line = lines[index]; 153  154             while(true){ 155                 if(loopType === 'back' && newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line); 156                 if(loopType === 'next') index = index === length - 1 ? 0 : index + 1; 157                 else if(loopType === 'back') index = index === 0 ? length - 1 : index - 1; 158  159                 line = lines[index]; 160                 if(loopType === 'next' && newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line); 161  162                 result_loop.count = line.nodes.length; 163                 if(result_loop.count !== 0){ 164                     result_loop.lines = lines; 165                     result_loop.loopType = loopType; 166                     result_loop.line = line; 167                     result_loop.indexed = index; 168                     return result_loop; 169                 } 170                  171                 if(indexed === index) break; 172  173             } 174  175         }, 176  177         //更新或创建交点的索引 178         setNodeIndex = (lines, index, loopType) => { 179             const line = lines[index], count = line.nodes.length; 180             if(loopType === undefined) loopType = line.loopType; 181             else if(line.loopType === undefined) line.loopType = loopType; 182  183             if(loopType === undefined) return; 184  185             if(line.nodeIndex === undefined){ 186                 line.nodeIndex = loopType === 'next' ? 0 : count - 1; 187                 line.nodeState = count === 1 ? 'end' : 'start'; 188                 line._loopType = loopType; 189             } 190  191             else{ 192                 if(line.nodeState === 'end' || line.nodeState === ''){ 193                     line.nodeState = ''; 194                     return; 195                 } 196  197                 if(line._loopType === 'next'){ 198                     line.nodeIndex += 1; 199  200                     if(line.nodeIndex === count - 1) line.nodeState = 'end'; 201                     else line.nodeState = 'run'; 202                 } 203  204                 else if(line._loopType === 'back'){ 205                     line.nodeIndex -= 1; 206  207                     if(line.nodeIndex === 0) line.nodeState = 'end'; 208                     else line.nodeState = 'run'; 209                 } 210  211             } 212  213         }, 214  215         //只有在跳线的时候才执行此方法, 如果跳线的话: 某条线上的交点必然在两端; 216         getLoopType = (lines, index, nodePoint) => { 217             const line = lines[index], lineNext = lines[index === lines.length - 1 ? 0 : index + 1], 218  219             model = lines === linesA ? polygon : this, 220             isLineBack = newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false, 221             isLineNext = newLines.includes(lineNext) === false && model.containsPoint(lineNext.x, lineNext.y) === false; 222              223             if(isLineBack && isLineNext){ 224                 const len = line.nodes.length; 225                 if(len >= 2){ 226                     if(line.nodes[len - 1].point.equals(nodePoint)) return 'next'; 227                     else if(line.nodes[0].point.equals(nodePoint)) return 'back'; 228                 } 229                  230                 else console.warn('路径复杂', line); 231                  232             } 233  234             else if(isLineNext){ 235                 return 'next'; 236             } 237  238             else if(isLineBack){ 239                 return 'back'; 240             } 241  242             return ''; 243         }, 244  245         //处理拥有交点的线 246         computeNodes = v => { 247             if(v === undefined) return; 248  249             setNodeIndex(v.lines, v.lines.indexOf(v.line), v.loopType); 250              251             // 252             const node = v.line.nodes[v.line.nodeIndex], model = v.lines === linesA ? polygon : this; 253             if(newLines.includes(node.point) === false) newLines.push(node.point); 254  255             var lines, index, loopType, line; 256  257             // 258             const lineR = v.lines === linesA ? node.lineB : node.lineA,  259             linesR = lineR === node.lineA ? linesA : linesB; 260             setNodeIndex(linesR, linesR.indexOf(lineR)); 261  262             const nodeState = lineR.nodeState !== undefined ? lineR.nodeState : v.line.nodeState; 263  264             switch(nodeState){ 265  266                 //不跳线 267                 case 'run':  268                     lines = v.lines;  269                     index = v.indexed;  270                     loopType = v.loopType; 271                     computeNodes(loop(lines, index, loopType)); 272                     break; 273  274                 //跳线 275                 case 'start': 276                 case 'end':  277                     lines = model === polygon ? linesB : linesA; 278                     line = lines === linesA ? node.lineA : node.lineB; 279                     index = lines.indexOf(line);  280                     loopType = getLoopType(lines, index, node.point); 281                      282                     if(loopType !== ''){ 283                         line.loopType = loopType; 284                         //if(loopType === 'back' && newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line); 285                         computeNodes(loop(lines, index, loopType)); 286                     } 287                     break; 288                  289  290                 default: break; 291  292             } 293  294         } 295  296         computeNodes(result_loop); 297      298         return newLines; 299     } 300  301 }

部分代码

 

注意: Polygon 目前并不完美

 

parameter:

    path: Array[x, y];

attribute:

    //只读属性
    path: Array[x, y];

method:
    add(x, y): this;            //x,y添加至path;
    containsPoint(x, y): Bool;  //x,y是否在多边形的内部(注意: 在路径上也返回 true)
    merge(polygon): this;       //合并; 假设polygon与this存在重叠的部分
 
 
这个时专门用于测试bug的ui控件:
js多边形(不规则的形状)之合并js多边形(不规则的形状)之合并

  1 /* PolygonTest 调试类 Polygon (可视化调试: 多边形之合并)   2    3     无任何属性, new就完事了!   4    5 */   6 class PolygonTest{   7    8     constructor(){   9         const path = [], box = new Box(),  10   11         car = new CanvasAnimateRender({  12             width: window.innerWidth,   13             height: window.innerHeight,  14         }).render(),   15       16         cab = car.add(new CanvasAnimateBox()).size(car.box.w, car.box.h, true);  17           18         function draw(obj){  19             console.log(obj);  20       21             target._redraw();  22       23             obj.forEach((v, i) => {  24                 const str = String(i),   25                 size = cab.textWidth(str, 20),   26                 width = size < 20 ? 20 : size;  27                   28                 cab.rect(2, box.set(v.x, v.y, width, width), 2).fill('#fff');  29                 cab.text(str, '#0000ff', box.pos(v.x + (width - size)/2, v.y));  30       31                 if(v.x1 === undefined) cab.stroke('#ffff00');  32                   33             });  34       35             car.redraw();  36         }  37       38       39         const target = {  40             title: '测试多边形之合并(此类并不完美)',  41             polygonA: null,  42             polygonB: null,  43             centerA: new Box(),  44             centerB: new Box(),  45             length: 0,  46             mergeType: 0,  47       48             merge(){  49                 if(this.polygonA === null || this.polygonB === null) return console.warn('必须已生成两个多边形');  50                 if(this.mergeType === 0) draw(this.polygonA.merge(this.polygonB));  51                 else draw(this.polygonB.merge(this.polygonA));  52                   53             },  54       55             setPolygonA(){  56                 this.polygonA = new Polygon(path.concat());  57                 this.centerA.setFromPolygon(this.polygonA, false);  58                 update_offsetNum();  59             },  60       61             setPolygonB(){  62                 this.polygonB = new Polygon(path.concat());  63                 this.centerB.setFromPolygon(this.polygonB, false);  64                 update_offsetNum();  65             },  66       67             generate(){  68                 if(path.length < 6) return console.warn('路径至少需要3个点');  69       70                 if(this.polygonA !== null && this.polygonB !== null){  71                     const _path = path.concat();  72                     this.clear();  73                     _path.forEach(v => path.push(v));  74                     this.generate();  75                     return;  76                 }  77       78                 else{  79                     cab.path(path, true);  80       81                     if(this.polygonA === null) this.setPolygonA();  82                     else this.setPolygonB();  83                     path.length = 0;  84                       85                 }  86                   87                 this._redraw();  88                 car.redraw();  89             },  90       91             clear(){  92                 path.length = 0;  93                 this.polygonA = this.polygonB = null;  94                 this.centerA.set(0,0,0,0);  95                 this.centerB.set(0,0,0,0);  96                 cab.clear();  97                 car.clear();  98             },  99      100             clearPath(){ 101                 path.length = 0; 102                 cab.clear(); 103                 this._redraw(); 104                 car.redraw(); 105             }, 106      107             _redraw(){ 108                 if(this.polygonA !== null){ 109                     cab.clear().path(this.polygonA.path).stroke('#00ff00', 1); 110                     this.sign(this.polygonA.path, 'rgba(0,255,0,1)', 'rgba(0,255,0,0.6)'); 111                     if(this.polygonB !== null){ 112                         cab.path(this.polygonB.path).stroke('#ff0000', 1); 113                         this.sign(this.polygonB.path, 'rgba(255,0,0,1)', 'rgba(255,0,0,0.6)'); 114                     } 115                      116                 } 117                  118             }, 119      120             //标记线的第一和第二个点 121             sign(path, color1, color2){ 122                 cab.rect(5, box.set(path[0]-5, path[1]-5, 10, 10), 2).fill(color1); 123                 cab.rect(5, box.set(path[2]-5, path[3]-5, 10, 10), 2).fill(color2); 124             }, 125      126             //偏移路径 127             offsetTimer: new Timer(()=>target.offset(), 300, 1, false), 128             offsetNum: new Point(0, 0), 129             offsetTarget: 'polygonA', 130             offsetVN: 'x', 131             offsetPoint: new Point(), 132             offset(){ 133                 if(this[this.offsetTarget] === null) return; 134                 path.length = 0; 135                  136                 const pathNew = path, pathOld = this[this.offsetTarget].path, point = this.offsetPoint,  137                 center = this.offsetTarget === 'polygonA' ? 'centerA' : 'centerB', 138                 x = this.offsetNum[this.offsetVN] * car.box[this.offsetVN === 'x' ? 'w' : 'h']; 139                  140                 for(let k = 0, len = pathOld.length; k < len; k+=2){ 141                     point.set(pathOld[k], pathOld[k+1]); 142                     point[this.offsetVN] = point[this.offsetVN] - this[center][this.offsetVN] + x; 143                     pathNew.push(point.x, point.y); 144                 } 145      146                 this[this.offsetTarget === 'polygonA' ? 'setPolygonA' : 'setPolygonB'](); 147                 path.length = 0; 148                 this._redraw(); 149                 car.redraw(); 150             }, 151      152             //克隆 153             clone(){ 154                 if(this[this.offsetTarget] === null) return; 155                 path.length = 0; 156                 this[this.offsetTarget].path.forEach(v => path.push(v)); 157                 this[this.offsetTarget === 'polygonA' ? 'setPolygonB' : 'setPolygonA'](); 158                  159                 path.length = 0; 160                 this._redraw(); 161                 car.redraw(); 162                  163             }, 164      165         }, 166      167         lineData = {type: 'line', value: '.title'}, 168      169         data = [ 170             { 171                 title: '项目', 172                 valueUrl: '.title', 173             }, 174             { 175                 explain: '路径长度', 176                 title: 'length', 177                 valueUrl: '.length', 178                 disable: true, 179             }, 180      181             { 182                 explain: '生成多边形', 183                 title: 'generate', 184                 valueUrl: '.generate', 185             }, 186      187             lineData, 188             { 189                 title: 'mergeType', 190                 valueUrl: '.mergeType', 191                 selectList: [{name: 'lineB to lineA', value: 0}, {name: 'lineA to lineB', value: 1}] 192             }, 193      194             { 195                 explain: '合并多边形', 196                 title: 'merge', 197                 valueUrl: '.merge', 198             }, 199             lineData, 200      201             { 202                 title: 'line', 203                 valueUrl: '.offsetTarget', 204                 selectList: [{name: 'lineA', value: 'polygonA'}, {name: 'lineB', value: 'polygonB'}], 205                 onChange: ()=> update_offsetNum(), 206             }, 207      208             { 209                 title: 'clone', 210                 valueUrl: '.clone', 211                 explain: '克隆形状后它们会重叠, 可以通过offset控件移动它们', 212             }, 213      214             { 215                 title: 'offset', 216                 valueUrl: '.offsetNum', 217                 range: {min: 0, max: 1, step: 0.01}, 218                 onChange: v => { 219                     v.scope.target.offsetVN = v.valueName; 220                     v.scope.target.offsetTimer.start(); 221                 }, 222             }, 223             lineData, 224      225             { 226                 explain: '清理路径和多边形', 227                 title: 'clearAll', 228                 valueUrl: '.clear', 229             }, 230      231             { 232                 explain: '清理路径', 233                 title: 'clearPath', 234                 valueUrl: '.clearPath', 235             }, 236      237         ], 238      239         ui = new CanvasAnimateUI(target, data, document.body, { 240             autoHeight: true, 241             width: 250, 242             globalUpdate: true, 243             defiendRange: {min: 0, max: car.box.w, step: 1}, 244             style: ` 245                 position: absolute; 246                 right: 2px; 247                 top: 2px; 248                 background: #fff; 249             `, 250         }).initUI(), 251      252      253         ui_length = ui.getView(ui.getData('.length')), 254         update_length = function (){ 255             target.length = path.length; //更新值 256             ui_length.update[0](); //更新ca 257             ui_length.redraw(); //更新画布 258         }, 259          260         ui_offsetNum = ui.getView(ui.getData('.offsetNum')), 261         update_offsetNum = function (){ 262             const center = target.offsetTarget === 'polygonA' ? 'centerA' : 'centerB'; 263             target.offsetNum.set(target[center].x / car.box.w, target[center].y / car.box.h);  //更新值 264             ui_offsetNum.update[0](); //更新ca 265             ui_offsetNum.update[1](); //更新ca 266             ui_offsetNum.redraw(); //更新画布 267         }; 268      269      270         //event 271         const cae = new CanvasAnimateEvent(car),  272         eventBox = new CanvasAnimate(); 273         eventBox.box.copy(car.box); 274      275         cae.add(eventBox, 'click', event => { 276             if(path.length >= 2) cab.line(path[path.length - 2], path[path.length - 1], event.pageX, event.pageY).stroke('#ffff00', 1); 277             else cab.rect(20/2, box.size(20, 20).pos(event.pageX - 10, event.pageY - 10), 2).fill('#ffff00'); 278      279             path.push(event.pageX, event.pageY); 280             car.redraw(); 281             update_length(); 282         }); 283      284      285     } 286  287 }

可视化调试: 多边形之合并

js多边形(不规则的形状)之合并

 

 

提取地址: https://pan.baidu.com/s/1TV1j5BeZ7ZhidCq7aQXePA

提取码: 1111