jcuts
Version:
1,528 lines • 55.5 kB
JavaScript
(function() {
var jmaths, jpaths;
(function() {
var define = function(factory) {
jmaths = factory();
};
define.amd = true;
(function(exportName) {
'use strict';
var exports = exports || {};
/**
* 数学函数收集
*
* @author 王集鹄(wangjihu,http://weibo.com/zswang)
* @version 2015-04-12
*/
/**
* 计算点到点之间的距离
*
* @param {Array[number,number]} a 坐标1
* @param {Array[number,number]} b 坐标2
* @return {number} 返回点与点间的距离
*/
function pointToPoint(a, b) {
return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2));
}
exports.pointToPoint = pointToPoint;
/**
* 计算点的角度
*
* @param {Array} origin 圆心坐标
* @param {Array} point 点坐标
* @return {number} 返回角度,单位:弧度
*/
function pointToAngle(origin, point) {
return Math.atan2(point[1] - origin[1], point[0] - origin[0]);
}
exports.pointToAngle = pointToAngle;
/**
* 旋转一个点坐标
*
* @param {Array} point 目标坐标
* @param {Array} center 中心点
* @param {number} angle 选择角度,单位:弧度
* @return {Array} 返回旋转后的坐标
*/
function rotatePoint(point, center, angle) {
var radius = pointToPoint(center, point); // 半径
angle = pointToAngle(center, point) + angle; // 角度
return [
center[0] + Math.cos(angle) * radius,
center[1] + Math.sin(angle) * radius
];
}
exports.rotatePoint = rotatePoint;
/**
* 贝赛尔公式,支持多维数组
*
* @param{Array[Array[number, ...],...]} items 每个参考点
* @param{number} rate 比率
* @return{number|Array[number, ...]} 返回
*/
function bezier(items, rate) {
if (!items || !items.length) {
return;
}
var first = items[0],
second = items[1];
var level = first instanceof Array ? first.length : 0; // 下标数,0为非数组
var i;
switch (items.length) {
case 1:
return level ? first.slice() : first; // 数组需要克隆,非数组直接返回
case 2:
if (level) { // 非数组
var result = [];
for (i = 0; i < level; i++) {
result[i] = bezier([first[i], second[i]], rate);
}
return result;
}
return first + (second - first) * rate;
default:
var temp = [];
for (i = 1; i < items.length; i++) {
temp.push(bezier([items[i - 1], items[i]], rate));
}
return bezier(temp, rate);
}
}
exports.bezier = bezier;
/**
* 将一条贝赛尔数组剪成两段
*
* @param {Array[Array[number, number],...]} items 贝赛尔每个参考点
* @param {number} rate 比率
* @return {Array[Array,Array]} 返回被裁剪后的两个贝赛尔数组
*/
function cutBezier(items, rate) {
if (!items || items.length < 2) {
return;
}
var ta = [];
var tb = [];
var ra = [];
var rb = [];
for (var i = 0; i < items.length; i++) {
ta.push(items[i]);
ra.push(bezier(ta, rate));
tb.unshift(items[items.length - i - 1]);
rb.unshift(bezier(tb, rate));
}
return [ra, rb];
}
exports.cutBezier = cutBezier;
/**
* 计算点到线段的距离
*
* @param {Array[number,number]} point 点坐标
* @param {Array[number,number]} a 线段坐标1
* @param {Array[number,number]} b 线段坐标2
* @return {number} 返回点到线段的距离
*/
function pointToLine(point, a, b) {
if (a[0] == b[0] && a[1] == b[1]) {
return 0;
}
var t = ((a[0] - b[0]) * (a[0] - point[0]) + (a[1] - b[1]) * (a[1] - point[1])) /
((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]));
return pointToPoint(point, bezier([a, b], Math.max(0, Math.min(1, t))));
}
exports.pointToLine = pointToLine;
/**
* 获取正负符号
*
* @param {number} x 数值
* @return 返回x的符号
*/
function sign(x) {
return x === 0 ? 0 : (x < 0 ? -1 : 1);
}
exports.sign = sign;
/**
* 获取两条线段的交点
*
* @see http://www.kevlindev.com/gui/math/intersection/Intersection.js
* @param {Array[number,number]} a 第一条线段坐标1
* @param {Array[number,number]} b 第一条线段坐标2
* @param {Array[number,number]} c 第二条线段坐标1
* @param {Array[number,number]} d 第二条线段坐标2
* @return {Array[number,number]} 返回两条线段的交点坐标
*/
function doubleLineIntersect(a, b, c, d) {
var ua_t = (d[0] - c[0]) * (a[1] - c[1]) - (d[1] - c[1]) * (a[0] - c[0]);
var ub_t = (b[0] - a[0]) * (a[1] - c[1]) - (b[1] - a[1]) * (a[0] - c[0]);
var u_b = (d[1] - c[1]) * (b[0] - a[0]) - (d[0] - c[0]) * (b[1] - a[1]);
if (u_b !== 0) {
var ua = ua_t / u_b;
var ub = ub_t / u_b;
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
return [
a[0] + ua * (b[0] - a[0]),
a[1] + ua * (b[1] - a[1])
];
}
}
}
exports.doubleLineIntersect = doubleLineIntersect;
/**
* 计算点到多边形的距离
*
* @param {Array[number,number]} point
* @param {Array[Array[number,number]]} polyline
* @return {number} 返回距离
*/
function pointToPolyline(point, polyline) {
if (!point || !polyline || !polyline.length) {
return;
}
var result = pointToPoint(point, polyline[0]);
for (var i = 1, l = polyline.length; i < l; i++) {
result = Math.min(result, pointToLine(point, polyline[i - 1], polyline[i]));
}
return result;
}
exports.pointToPolyline = pointToPolyline;
/**
* 判断点是否在多边形中
*
* @param {Array} p 点坐标
* @param {Array} polygon 多边形坐标
* @return {boolean} 返回点是否在多边形中
*/
function pointInPolygon(p, polygon) {
var cross = 0;
for (var i = 0; i < polygon.length; i++) {
var p1 = polygon[i];
var p2 = polygon[(i + 1) % polygon.length];
if (p1[1] === p2[1]) {
continue;
}
if (p[1] < Math.min(p1[1], p2[1])) {
continue;
}
if (p[1] >= Math.max(p1[1], p2[1])) {
continue;
}
var x = (p[1] - p1[1]) * (p2[0] - p1[0]) / (p2[1] - p1[1]) + p1[0];
if (x > p[0]) {
cross++;
}
}
return cross % 2 === 1;
}
exports.pointInPolygon = pointInPolygon;
/**
* 生成正多边形顶点
*
* @param {number} edges 边数
* @param {number} x 中心坐标 x
* @param {number} y 中心坐标 y
* @param {number} radius 半径
* @param {=number} starting 默认为 0
* @return {Array} 返回正多边形顶点数组
*/
function regularPolygon(edges, x, y, radius, starting) {
var angle = starting || 0;
var result = [];
for (var i = 0; i < edges; i++) {
var a = angle + i / edges * Math.PI * 2;
var p = [x + Math.cos(a) * radius, y + Math.sin(a) * radius];
result.push(p);
}
return result;
}
exports.regularPolygon = regularPolygon;
if (typeof define === 'function') {
if (define.amd || define.cmd) {
define(function() {
return exports;
});
}
}
else if (typeof module !== 'undefined' && module.exports) {
module.exports = exports;
}
else {
window[exportName] = exports;
}
})('jmaths');
var define = function(factory) {
jpaths = factory();
};
define.amd = true;
(function(exportName) {
'use strict';
var exports = exports || {};
/**
* jpaths
*
* @file 一个简单绘图库,兼容 svg、vml、canvas,路径只支持其交集。
* @author 王集鹄(wangjihu,http://weibo.com/zswang)
* @version 2015-04-11
*/
/**
* 是否 IE
*/
var ie = document.all && document.attachEvent;
/**
* IE 放大的倍数,保证精度
*/
var ieZoom = 1000;
/*
* 是否 IE9+
*/
var ie9plus = ie && window.XMLHttpRequest && window.addEventListener && document.documentMode >= 9;
/*
* 渲染模式 'svg' | 'vml' | 'canvas'
*/
var renderMode = !ie || ie9plus ? 'svg' : 'vml';
/*
* 容器列表,如果容器是一样的,则不用生成新的 svg 对象
*/
var parentList = [];
/**
* 格式化函数
*
* @param {string} template 模板
* @param {Object} json 数据项
*/
function format(template, json) {
return template.replace(/#\{(.*?)\}/g, function(all, key) {
return json && (key in json) ? json[key] : "";
});
}
/**
* 解析路径字符串中的详情
*
* @param {string} path 路径表达式
* @return {Array} 返回每个子路径
*/
function parsePath(path) {
var current = [0, 0];
var movePos = [0, 0];
var result = [];
String(path).replace(
/([mlcza])((\s*,?\s*([+-]?\d+(?:\.\d+)?)+)*)/gi,
function(all, flag, params) {
switch (flag) {
case 'M':
case 'L':
var passing = false; // 是否已经处理过参数
params.replace(
/\s*,?\s*([+-]?\d+(?:\.\d+)?)\s*,?\s*([+-]?\d+(?:\.\d+)?)/gi,
function(all, x, y) {
current = [+x, +y];
result.push([flag === 'L' || passing ? 'L' : 'M', current]);
passing = true;
if (flag === 'M') {
movePos = current;
}
}
);
break;
case 'C':
params.replace(
/\s*,?\s*([+-]?\d+(?:\.\d+)?)\s*,?\s*([+-]?\d+(?:\.\d+)?)\s*,?\s*([+-]?\d+(?:\.\d+)?)\s*,?\s*([+-]?\d+(?:\.\d+)?)\s*,?\s*([+-]?\d+(?:\.\d+)?)\s*,?\s*([+-]?\d+(?:\.\d+)?)/gi,
function(all, x1, y1, x2, y2, x, y) {
current = [0, 0];
result.push(['C', [+x1 + current[0], +y1 + current[1], +x2 + current[0], +y2 + current[1], +x + current[0], +y + current[1]]]);
current = [+x + current[0], +y + current[1]];
});
break;
case 'Z':
result.push(['Z']);
current = movePos;
break;
}
}
);
return result;
}
/**
* 矢量路径类
*
* @param {Object} options 配置
* @param {string|Element} options.parent 容器
* @param {string} options.fill 填充色
* @param {number} options.fillOpacity 填充透明度
* @param {string} options.stroke 描边色
* @param {number} options.strokeOpacity 描边透明度
* @param {number} options.strokeWidth 描边宽度
* @param {string} options.path 路径
*/
function Path(options) {
options = options || {};
if (typeof options.parent === 'string') {
this.parent = document.getElementById(options.parent);
} else {
this.parent = options.parent || document.body;
}
this.fill = options.fill || 'none';
this.fillOpacity = options.fillOpacity || options['fill-opacity'] || 1;
this.stroke = options.stroke || 'black';
this.strokeWidth = options.strokeWidth || options['stroke-width'] || 1;
this.strokeOpacity = options.strokeOpacity || options['stroke-opacity'] || 1;
this.path = options.path || 'M 0,0';
// 处理相同的容器
var parentInfo;
for (var i = parentList.length - 1; i >= 0; i--) {
var item = parentList[i];
if (item.parent === this.parent) {
parentInfo = item;
break;
}
}
if (/^canvas$/i.test(this.parent.tagName)) {
renderMode = 'canvas';
this.pathDetails = parsePath(this.path);
this.repaint(true);
}
var div;
switch (renderMode) {
case 'canvas':
this.canvas = this.parent;
if (parentInfo) {
this.canvasPaths = parentInfo.paths;
} else {
this.canvasPaths = [];
parentList.push({
parent: this.parent,
paths: this.canvasPaths
});
}
this.canvasPaths.push(this);
this.repaint(true);
break;
case 'svg':
div = document.createElement('div');
div.innerHTML = format( /*#*/ "\n<svg width=100% height=100% xmlns=\"http://www.w3.org/2000/svg\">\n <path fill=\"#{fill}\"\n fill-rule=\"evenodd\"\n stroke-linejoin=\"round\"\n fill-opacity=\"#{fillOpacity}\"\n stroke=\"#{stroke}\"\n stroke-opacity=\"#{strokeOpacity}\"\n stroke-width=\"#{strokeWidth}\" d=\"#{path}\"\n</svg>\n", this);
this.elementPath = div.lastChild.lastChild;
if (parentInfo) {
this.element = parentInfo.element;
this.element.appendChild(this.elementPath);
} else {
this.element = div.lastChild;
parentList.push({
parent: this.parent,
element: this.element
});
this.parent.appendChild(this.element);
}
break;
case 'vml':
div = document.createElement('div');
this.filled = this.fill == 'none' ? 'f' : 't';
this.stroked = this.stroke == 'none' ? 'f' : 't';
this.zoom = ieZoom;
div.innerHTML = format( /*#*/ "\n<v:shape class=\"jpaths_path_shape jpaths_vml\"\n coordsize=\"#{zoom},#{zoom}\"\n stroked=\"#{stroked}\"\n filled=\"#{filled}\"\n path=\"#{path}\">\n <v:stroke class=\"jpaths_vml\"\n opacity=\"#{strokeOpacity}\"\n color=\"#{stroke}\"\n weight=\"#{strokeWidth}\">\n </v:stroke>\n <v:fill class=\"jpaths_vml\"\n opacity=\"#{fillOpacity}\"\n color=\"#{fill}\">\n </v:fill>\n</v:shape>\n", this);
this.elementPath = div.lastChild;
if (parentInfo) {
this.element = parentInfo.element;
this.element.appendChild(this.elementPath);
} else {
this.element = div;
div.className = 'jpaths_path_panel';
parentList.push({
parent: this.parent,
element: this.element
});
this.parent.appendChild(this.element);
}
this.elementFill = this.elementPath.getElementsByTagName('fill')[0];
this.elementStroke = this.elementPath.getElementsByTagName('stroke')[0];
break;
}
}
/**
* 绘制路径
*
* @params {boolean} all 是否全部更新
* @see http://code.google.com/p/canvg/
*/
Path.prototype.repaint = function(all) {
if (!this.canvas) {
return;
}
var context = this.canvas.getContext('2d');
if (!context) {
return;
}
var i;
if (all) {
context.save();
context.fillStyle = 'transparent';
context.globalCompositeOperation = 'copy'; // 彻底清除
context.fillRect(0, 0, this.canvas.width, this.canvas.height);
context.restore();
for (i = 0; i < this.canvasPaths.length; i++) {
this.canvasPaths[i].repaint();
}
return;
}
if (!this.pathDetails) {
return;
}
context.save();
context.beginPath();
var current = [0, 0]; // 当前坐标
var movePos = [0, 0]; // 位移坐标
for (i = 0; i < this.pathDetails.length; i++) {
var item = this.pathDetails[i];
switch (item[0]) {
case 'Z':
context.closePath();
current = movePos;
break;
case 'C':
context.bezierCurveTo(
item[1][0],
item[1][1],
item[1][2],
item[1][3],
item[1][4],
item[1][5]
);
current = [item[1][2], item[1][3]];
break;
case 'L':
current = [item[1][0], item[1][1]];
context.lineTo(item[1][0], item[1][1]);
break;
case 'M':
current = [item[1][0], item[1][1]];
movePos = [item[1][0], item[1][1]];
context.moveTo(item[1][0], item[1][1]);
break;
}
}
if (this.stroke !== 'none') {
context.strokeStyle = this.stroke;
context.stroke();
}
context.lineWidth = this.strokeWidth;
if (this.fill !== 'none') {
context.fillStyle = this.fill;
context.fill();
}
context.restore();
};
/**
* 设置或获取属性
*
* @param {Object} values
* @param {boolean} batch 是否正在批处理
* @or
* @param {string} name
* @param {string} value
* @param {boolean} batch 是否正在批处理
* @or
* @param {string} name
* @return {Any} 返回该属性值
*/
Path.prototype.attr = function(name, value) {
if (this.freed) {
return;
}
if (arguments.length === 1) {
if (typeof name === 'string') {
if (name === 'stroke-width') {
name = 'strokeWidth';
}
return this[name];
}
if (typeof name === 'object') {
for (var p in name) {
this.attr(p, name[p], true);
}
return this;
}
} else if (arguments.length > 1) {
switch (name) {
case 'path':
if (this.path === value) {
break;
}
this.path = value;
switch (renderMode) {
case 'svg':
this.elementPath.setAttribute('d', value || 'M 0,0');
break;
case 'vml':
this.elementPath.path = String(value || 'M 0,0')
.replace(/\d+(\.\d+)?/g, function(number) { // 清理小数
return parseInt(number * ieZoom);
})
.replace(/z/ig, 'X'); // 处理闭合
break;
case 'canvas':
this.pathDetails = parsePath(this.path);
this.repaint(true);
break;
}
break;
case 'fill':
if (this.fill === value) {
break;
}
this.fill = value;
switch (renderMode) {
case 'svg':
this.elementPath.setAttribute('fill', value);
break;
case 'vml':
this.elementPath.Filled = this.filled = this.fill === 'none' ? 'f' : 't';
this.elementFill.Color = value;
break;
case 'canvas':
if (batch) {
this.repaint(true);
}
break;
}
break;
case 'stroke':
if (this.stroke === value) {
break;
}
this.stroke = value;
switch (renderMode) {
case 'svg':
this.elementPath.setAttribute('stroke', value);
break;
case 'vml':
this.elementPath.Stroked = this.stroked = this.stroke === 'none' ? 'f' : 't';
this.elementStroke.Color = value;
break;
case 'canvas':
if (batch) {
this.repaint(true);
}
break;
}
break;
case 'fillOpacity':
case 'fill-opacity':
if (this.fillOpacity === value) {
break;
}
this.fillOpacity = value;
switch (renderMode) {
case 'svg':
this.elementPath.setAttribute('fill-opacity', value);
break;
case 'vml':
this.elementFill.Opacity = Math.min(Math.max(0, value), 1);
break;
case 'canvas':
if (batch) {
this.repaint(true);
}
break;
}
break;
case 'strokeOpacity':
case 'stroke-opacity':
if (this.strokeOpacity === value) {
break;
}
this.strokeOpacity = value;
switch (renderMode) {
case 'svg':
this.elementPath.setAttribute('stroke-opacity', value);
break;
case 'vml':
this.elementStroke.Opacity = Math.min(Math.max(0, value), 1);
break;
case 'canvas':
if (batch) {
this.repaint(true);
}
break;
}
break;
case 'strokeWidth':
case 'stroke-width':
if (this.strokeWidth == value) {
break;
}
this.strokeWidth = value;
switch (renderMode) {
case 'svg':
this.elementPath.setAttribute('stroke-width', value);
break;
case 'vml':
this.elementStroke.weight = value;
break;
case 'canvas':
if (batch) {
this.repaint(true);
}
break;
}
break;
}
return this;
}
};
/**
* 释放资源
*/
Path.prototype.free = function() {
if (this.freed) {
return;
}
this.freed = true;
switch (renderMode) {
case 'svg':
case 'vml':
this.elementPath.parentNode.removeChild(this.elementPath);
this.elementPath = null;
break;
case 'canvas':
for (var i = 0; i < this.canvasPaths.length; i++) {
if (this.canvasPaths[i] === this) {
this.canvasPaths.splice(i, 1);
this.repaint(true);
break;
}
}
break;
}
};
function create(options) {
return new Path(options);
}
if (renderMode === 'vml') {
document.createStyleSheet().cssText = format( /*#*/ "\n.#{this}_vml {\n behavior: url(#default#VML);\n}\n.#{this}_path_shape {\n width: 1px;\n height: 1px;\n padding: 0;\n margin: 0;\n left: 0;\n top: 0;\n position: absolute;\n}\n.#{this}_path_panel {\n width: 100%;\n height: 100%;\n overflow: hidden;\n padding: 0;\n margin: 0;\n position: relative;\n}\n", exportName);
}
exports.create = create;
if (typeof define === 'function') {
if (define.amd || define.cmd) {
define(function() {
return exports;
});
}
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = exports;
} else {
window[exportName] = exports;
}
})('jpaths');
})();
(function(exportName) {
'use strict';
var exports = exports || {};
/**
* 比较两个多边形的相似度
*
* @param {Array} a 多边形 1 // [[1, 2], [3, 4], [4, 5]]
* @param {Array} b 多边形 2 // [[1, 2], [3, 4], [4, 7]]
* @return {number} 返回两个多边形的相似度,范围:0~1,为 1 表示完全相等
*/
function diffPolygon(a, b) {
var canvas1 = document.createElement("canvas");
var canvas2 = document.createElement("canvas");
var minX = Infinity;
var minY = Infinity;
var maxX = 0;
var maxY = 0;
var point;
var i;
for (i = 0; i < a.length; i++) {
point = a[i];
minX = (minX > point[0]) ? point[0] : minX;
minY = (minY > point[1]) ? point[1] : minY;
maxX = (maxX < point[0]) ? point[0] : maxX;
maxY = (maxY < point[1]) ? point[1] : maxY;
}
for (i = 0; i < b.length; i++) {
point = b[i];
minX = (minX > point[0]) ? point[0] : minX;
minY = (minY > point[1]) ? point[1] : minY;
maxX = (maxX < point[0]) ? point[0] : maxX;
maxY = (maxY < point[1]) ? point[1] : maxY;
}
var width = maxX - minX + 1;
var height = maxY - minY + 1;
canvas1.width = width;
canvas1.height = height;
canvas2.width = width;
canvas2.height = height;
drawPolygon(canvas1, a, minX, minY);
drawPolygon(canvas2, b, minX, minY);
var similarity = 1 - calculateDiff(canvas1, canvas2, width, height);
return similarity;
}
exports.diffPolygon = diffPolygon;
/**
* 比较两个图形的相似度
*
* @param {Object} a 图形 1 // [[1, 2], [3, 4], [4, 5]]
* @param {Object} b 图形 2 // [[1, 2], [3, 4], [4, 7]]
* @return {number} 返回两个多边形的相似度,范围:0~1,为 1 表示完全相等
*/
function diffShape(a, b) {
if (a.radius > b.radius) {
var t = a;
a = b;
b = t;
}
return diffPolygon(a.polygon,
coordinateTransformation(b.polygon, a.base, b.base));
}
exports.diffShape = diffShape;
/**
* 获得完整剪纸的多边形
*
* @param {Array} polygon 基础多边线 // [[x, y], [x1, y1], ...]
* @param {number} edges 边数 // 5
* @param {number} center 中心点
* @return {Array} 选择和映射后的多边形数组 [[[x, y], [x1, y1], ...], [[x, y], [x1, y1], ...] ...]
*/
function getCutPolygons(edges, center, polygon) {
//计算与之对称的多边形的端点集
var newPolygon = [];
var deltaAngle = 360 / (4 * edges);
var i;
var arc;
for (i = 0; i < polygon.length; i++) {
var m = polygon[i][0];
var n = polygon[i][1];
var angle;
if (n === center[1]) {
angle = 90;
}
else {
angle = Math.atan(Math.abs(m - center[0]) / Math.abs(n - center[1])) * 180 / Math.PI;
}
if (m > center[0]) {
angle = deltaAngle - angle;
}
else {
angle = deltaAngle + angle;
}
arc = 2 * angle * Math.PI / 180;
newPolygon.push(jmaths.rotatePoint(polygon[i], center, arc));
}
//将两个相互对称的端点集旋转相应的角度
var polygons = [];
polygons.push(polygon);
polygons.push(newPolygon);
for (i = 1; i < edges; i++) {
arc = (i * 360 / edges) * Math.PI / 180;
var rPolygon = [];
var rNewPolygon = [];
for (var j = 0; j < polygon.length; j++) {
rPolygon.push(jmaths.rotatePoint(polygon[j], center, arc));
rNewPolygon.push(jmaths.rotatePoint(newPolygon[j], center, arc));
}
polygons.push(rPolygon);
polygons.push(rNewPolygon);
}
return polygons;
}
exports.getCutPolygons = getCutPolygons;
/**
* 在canvas上根据多边形端点数组绘制多边形
* 要求端点数组有序
*
* @param {canvas DOM element} canvas 画布元素
* @param {Array} vertex 多边形端点数组 // [[1, 2], [3, 4], [4, 7]]
* @param {number} offsetX x轴偏移量,默认为0
* @param {number} offsetY y轴偏移量,默认为0
* @param {string} color 多边形填充色,默认为黑色
*/
function drawPolygon(canvas, vertex, offsetX, offsetY, color) {
offsetX = offsetX || 0;
offsetY = offsetY || 0;
var context = canvas.getContext('2d');
context.beginPath();
var beginX;
var beginY;
for (var i = 0; i < vertex.length; i++) {
var point = vertex[i];
var x = point[0] - offsetX;
var y = point[1] - offsetY;
if (i === 0) {
beginX = x;
beginY = y;
context.moveTo(x, y);
}
else {
context.lineTo(x, y);
}
}
context.lineTo(beginX, beginY);
context.closePath();
if (color) {
context.fillStyle = color;
}
context.fill();
}
/**
* 比较两个canvas黑色多边形的不同度
*
* @param {canvas DOM element} canvas1 要比较的第一个画布元素 1
* @param {canvas DOM element} canvas2 要比较的第二个画布元素 2
* @param {number} width 要比较的绘制区域的宽 3
* @param {number} height 要比较的绘制区域的高 4
* @return {number} 返回两个canvas黑色多边形的不同度,范围:0~1,为 1 表示完全不同
*/
function calculateDiff(canvas1, canvas2, width, height) {
var context1 = canvas1.getContext('2d');
var context2 = canvas2.getContext('2d');
var imgData1 = context1.getImageData(0, 0, width, height);
var imgData2 = context2.getImageData(0, 0, width, height);
var diffPixelNum = 0;
var baseNum = 0;
for (var i = 0; i < imgData1.data.length; i += 4) {
baseNum += imgData1.data[i + 3] / 255;
baseNum += imgData2.data[i + 3] / 255;
var pixelDiff = Math.abs(imgData1.data[i + 3] - imgData2.data[i + 3]) / 255;
diffPixelNum += pixelDiff;
}
return diffPixelNum / baseNum;
}
/**
* 多边形掉落效果
*
* @param {Array} vertex 多边形端点集
* @param {string} color 多边形填充色
*/
function polygonDrop(vertex, color, container) {
//绘制多边形
var canvas = document.createElement("canvas");
var minX = Infinity;
var minY = Infinity;
var maxX = 0;
var maxY = 0;
for (var i = 0; i < vertex.length; i++) {
var point = vertex[i];
minX = (minX > point[0]) ? point[0] : minX;
minY = (minY > point[1]) ? point[1] : minY;
maxX = (maxX < point[0]) ? point[0] : maxX;
maxY = (maxY < point[1]) ? point[1] : maxY;
}
var width = maxX - minX + 1;
var height = maxY - minY + 1;
canvas.width = width;
canvas.height = height;
drawPolygon(canvas, vertex, minX, minY, color);
//添加掉落效果
var paperWrap = document.createElement('div');
paperWrap.className += 'paper-wrap ';
paperWrap.className += 'fall ';
var body = document.getElementsByTagName('body')[0];
if (container) {
body = container;
}
var rect = document.body.getBoundingClientRect();
var top = rect.top + minY;
var left = rect.left + minX;
paperWrap.style.width = width + "px";
paperWrap.style.height = height + "px";
paperWrap.style.top = top + "px";
paperWrap.style.left = left + "px";
var area = width * height;
if (area < 1600) {
var innerWrap = document.createElement('div');
innerWrap.className += 'rotate ';
innerWrap.appendChild(canvas);
paperWrap.appendChild(innerWrap);
} else if (area > 10000) {
var innerWrap = document.createElement('div');
innerWrap.className += 'sway3d ';
innerWrap.appendChild(canvas);
paperWrap.appendChild(innerWrap);
} else {
var innerWrap = document.createElement('div');
innerWrap.className += 'rotate-slow ';
innerWrap.appendChild(canvas);
var outerWrap = document.createElement('div');
outerWrap.className += 'sway2d ';
outerWrap.appendChild(innerWrap);
paperWrap.appendChild(outerWrap);
}
body.appendChild(paperWrap);
setTimeout(function () {
body.removeChild(paperWrap);
}, 2800);
}
exports.polygonDrop = polygonDrop;
/**
* 格式化函数
*
* @param {string} template 模板
* @param {Object} json 数据项
*/
function format(template, json) {
return template.replace(/#\{(.*?)\}/g, function(all, key) {
return json && (key in json) ? json[key] : "";
});
}
exports.format = format;
/**
* 坐标系转换
*
* @param {Array} polygon 多边形
* @param {Object} fromBase 源坐标信息
* @param {Array} fromBase.center 中心坐标
* @param {Array} fromBase.radius 中心坐标
* @param {Object} toBase 源坐标信息
* @param {Array} toBase.center 中心坐标
* @param {Array} toBase.radius 中心坐标
* @return {Array} 返回转换后的多边形
*/
function coordinateTransformation(polygon, fromBase, toBase) {
var result = [];
console.log(JSON.stringify(polygon));
polygon.forEach(function(item) {
var point = item.slice();
var t = fromBase.radius / toBase.radius;
var d = jmaths.pointToPoint(fromBase.center, item) / t;
var a = jmaths.pointToAngle(fromBase.center, item);
point[0] = toBase.center[0] + Math.cos(a) * d;
point[1] = toBase.center[1] + Math.sin(a) * d;
result.push(point);
});
console.log(JSON.stringify(result));
return result;
}
exports.coordinateTransformation = coordinateTransformation;
/**
* 创建剪纸对象
*
* @param {number} edges 边数 4 - 7
* @param {=Array} center 中心坐标
* @param {=number} radius 半径
* @return {Object} 返回剪纸对象
*/
function createPaper(edges, center, radius) {
var instance = {};
center = center || [0, 0];
radius = radius || 100;
instance.getCenter = function() {
return center;
};
instance.getRadius = function() {
return radius;
};
/**
* 作为模型的路径
*/
// flag => synechiaA: 粘黏 A, synechiaB: 粘黏 B, boundary: 边界, scar: 痕迹
// [[x0, y0], [x1, y1], flag]]
var modelPath = [];
/**
* 裁剪的路径
*
* @type {Array}
*/
var cutModelPath = [];
// init
var _allPolygon =
jmaths.regularPolygon(edges * 2, center[0], center[1], radius, -Math.PI * 0.5 - Math.PI / edges / 2);
// ======================
// start next
// +-------------+
// X X
// X X
// X X
// X X
// X X
// X X
// X
// center
// =======================
function rebuild() {
var start = _allPolygon[0];
var next = _allPolygon[1];
modelPath = [];
cutModelPath = [];
modelPath.push([start, center, 'synechiaA']);
modelPath.push([center, next, 'synechiaB']);
modelPath.push([next, start, 'boundary']);
}
instance.rebuild = rebuild;
rebuild();
/**
* 获取完整绘制路径
*
* @return {string} 返回完整绘制路径
*/
function getFullPath() {
var m = jmaths.regularPolygon(edges * 2, x, y, radius, -Math.PI * 0.5 - Math.PI / edges / 2);
// M #{center} L #{start}
return format('M #{start} L #{lines} Z', {
start: m[0],
lines: m.slice(1).join(' '),
center: [x, y]
});
}
instance.getFullPath = getFullPath;
/**
* 获取模型绘制路径
*
* @return {string} 返回模型绘制路径
*/
function getModelPath() {
var m = [];
modelPath.forEach(function(line) {
m.push([line[0].slice(), line[1].slice()]);
});
// console.log(JSON.stringify(m));
return format('M #{start} L #{lines}', {
start: m[0][0],
lines: m.join(' ')
});
}
/**
* 获取模型多边形
*
* @return {Array} 返回多边形
*/
function getModelPolygon() {
var result = [];
modelPath.forEach(function(line) {
result.push(line[0].slice());
});
return result;
}
instance.getModelPolygon = getModelPolygon;
/**
* 获取模型多边形
*
* @return {Array} 返回多边形
*/
function getCutPolygon() {
var result = [];
cutModelPath.forEach(function(line) {
result.push(line[0].slice());
});
return result;
}
instance.getCutPolygon = getCutPolygon;
/**
* 获取模型绘制路径
*
* @return {string} 返回模型绘制路径
*/
function getCutModelPath() {
var m = [];
cutModelPath.forEach(function(line) {
m.push([line[0].slice(), line[1].slice()]);
});
return format('M #{start} L #{lines}' +
'M #{start} m -5,0 h 10 M #{start} m 0,-5 v 10' +
'M #{end} m -8,0 h 16 M #{end} m 0,-8 v 16', {
start: m[0][0],
end: m[m.length - 1][1],
lines: m.join(' ')
});
}
instance.getCutModelPath = getCutModelPath;
/**
* 剪切纸片
*
* @param {Array} polygon 剪刀经过的路径
* @return {boolean} 剪切是否成功
*/
function cut(polygon) {
if (!polygon || polygon.length < 2) {
return;
}
function doCut(startModelIndex, endModelIndex) {
var a = [];
var b = [];
var start = cutModelPath[0][0];
var end = cutModelPath[cutModelPath.length - 1][1];
var modelIndex;
var temp;
var i;
var j;
if (startModelIndex !== endModelIndex) {
modelIndex = startModelIndex;
temp = start;
for (i = 0; i < modelPath.length; i++) {
next = modelPath[modelIndex][1];
if (modelIndex === endModelIndex) {
a.push([
temp,
end,
modelPath[modelIndex][2]
]);
for (j = cutModelPath.length - 1; j >= 0; j--) {
a.push([
cutModelPath[j][1],
cutModelPath[j][0],
cutModelPath[j][2]
]);
}
break;
}
a.push([
temp,
next,
modelPath[modelIndex][2]
]);
temp = next;
modelIndex = (modelIndex + 1) % modelPath.length;
}
modelIndex = startModelIndex;
temp = start;
for (i = 0; i < modelPath.length; i++) {
next = modelPath[modelIndex][0];
if (modelIndex === endModelIndex) {
b.push([
temp,
end,
modelPath[modelIndex][2]
]);
for (j = cutModelPath.length - 1; j >= 0; j--) {
b.push([
cutModelPath[j][1],
cutModelPath[j][0],
cutModelPath[j][2]
]);
}
break;
}
b.push([
temp,
next,
modelPath[modelIndex][2]
]);
temp = next;
modelIndex--;
if (modelIndex < 0) {
modelIndex = modelPath.length - 1;
}
}
}
else { // 在同一条边上
if (jmaths.pointToPoint(modelPath[startModelIndex][0], start) <
jmaths.pointToPoint(modelPath[startModelIndex][0], end)) {
a.push([
modelPath[startModelIndex][0],
start,
modelPath[startModelIndex][2]
]);
for (j = 0; j < cutModelPath.length; j++) {
a.push(cutModelPath[j]);
}
a.push([
end,
modelPath[startModelIndex][1],
modelPath[startModelIndex][2]
]);
for (i = 1; i < modelPath.length; i++) {
a.push(modelPath[
(startModelIndex + i) % modelPath.length]);
}
for (j = 0; j < cutModelPath.length; j++) {
b.push(cutModelPath[j]);
}
b.push([
end,
start,
modelPath[startModelIndex][2]
]);
}
else { // 逆方向
a.push([
start,
modelPath[startModelIndex][1],
modelPath[startModelIndex][2]
]);
for (i = 1; i < modelPath.length; i++) {
a.push(modelPath[
(startModelIndex + i) % modelPath.length]);
}
a.push([
modelPath[startModelIndex][0],
end,
modelPath[startModelIndex][2]
]);
for (j = cutModelPath.length - 1; j >= 0; j--) {
a.push([
cutModelPath[j][1],
cutModelPath[j][0],
cutModelPath[j][2]
]);
}
b.push([
end,
start,
modelPath[startModelIndex][2]
]);
for (j = 0; j < cutModelPath.length; j++) {
b.push(cutModelPath[j]);
}
}
}
var synechiaA = 0;
var countA = {};
a.forEach(function(item) {
countA[item[2]] = true;
if (item[2].indexOf('synechia') === 0) {
synechiaA += jmaths.pointToPoint(item[0], item[1]);
}
});
var synechiaB = 0;
var countB = {};
b.forEach(function(item) {
countB[item[2]] = true;
if (item[2].indexOf('synechia') === 0) {
synechiaB += jmaths.pointToPoint(item[0], item[1]);
}
});
if (countA.synechiaA && countA.synechiaB) {
if (!countB.synechiaA || !countB.synechiaB) {
modelPath = a;
cutModelPath = b;
return;
}
}
else if (countB.synechiaA && countB.synechiaB) {
if (!countA.synechiaA || !countA.synechiaB) {
modelPath = b;
cutModelPath = a;
return;
}
}
if (synechiaA >= synechiaB) {
modelPath = a;
cutModelPath = b;
}
else {
modelPath = b;
cutModelPath = a;
}
}
function cutSub(intersect, polygonIndex, modelIndex) {
cutModelPath = [];
var start = intersect;
for (var i = polygonIndex; i < polygon.length; i++) {
var next = polygon[i];
for (var j = 0; j < modelPath.length; j++) {
var line = modelPath[j];
if (i === polygonIndex && modelIndex === j) {
continue;
}
var p = jmaths.doubleLineIntersect(
start, next, line[0], line[1]);
if (p) {
cutModelPath.push([start, p, 'scar']);
doCut(modelIndex, j);
return;
}
}
cutModelPath.push([start, next, 'scar']);
start = next;
}
}
// 起点和终点不能在纸里
var modelPolygon = getModelPolygon();
if (jmaths.pointInPolygon(polygon[0], modelPolygon) ||
jmaths.pointInPolygon(polygon[polygon.length - 1], modelPolygon)) {
return;
}
var i;
var j;
// 稀释路径
if (polygon.length >= 3) {
var tempPolygon = [polygon[0]];
for (i = 1; i < polygon.length - 1; i++) {
var a = polygon[i - 1];
var b = polygon[i];
var c = polygon[i + 1];
if (jmaths.pointToLine(b, a, c) > 0.27) {
tempPolygon.push(b);
}
}
tempPolygon.push(polygon[polygon.length - 1]);
polygon = tempPolygon;
}
// 自身不要碰撞
for (i = 1; i < polygon.length - 2; i++) {
var lineA = [polygon[i - 1], polygon[i]];
for (j = i + 2; j < polygon.length; j++) {
var lineB = [polygon[j - 1], polygon[j]];
if (jmaths.doubleLineIntersect(
lineA[0], lineA[1], lineB[0], lineB[1])) {
return;
}
}
}
var start = polygon[0];
for (i = 1; i < polygon.length - 1; i++) {
var next = polygon[i];
for (j = 0; j < modelPath.length; j++) {
var line = modelPath[j];
var intersect = jmaths.doubleLineIntersect(
start, next, line[0], line[1]);
if (intersect) { // 出现相交
cutSub(intersect, i, j);
// console.log(JSON.stringify(polygon));
return true;
}
}
start = next;
}
}
instance.cut = cut;
instance.getModelPath = getModelPath;
return instance;
}
exports.createPaper = createPaper;
/**
* 创建剪纸游戏实例
*
* @param {Object} options 配置项
* @param {Element|string} options.container 容器,选择器或者元素对象
* @param {=number} options.edges 边数,3~6,默认 6
* @param {=string} options.stroke 描边颜色
* @param {=string} options.fill 填充颜色
* @param {=string} options.cutStroke 切线描边颜色
* @param {=Function} options.onchange 用户剪切纸张
* @return {Object} 返回游戏实例
*/
function createGame(options) {
options = options || {};
var container = typeof options.container === 'string' ?
document.querySelector(options.container) :
options.container || document.body;
var edges = options.edges || 6;
var onchange = options.onchange;
var instance = {};
var status = 'running'; // stop
var downPoint;
var points;
var center = [container.clientWidth / 2, container.clientHeight / 2 * 1.8];
var radius = Math.min(container.clientWidth, container.clientHeight) * 0.8;
var paper = createPaper(edges, center, radius);
var paperBackgrund = jpaths.create({
parent: container,
stroke: options.stroke || 'black',
fill: options.fill || 'none'
});
var paperHint = jpaths.create({
parent: container,
stroke: options.cutStroke || 'green',
strokeWidth: 2
});
function render() {
paperBackgrund.attr({
path: paper.getModelPath()
});
}
render();
console.log('createGame');
/**
* 返回当前用户剪辑留下的形状
*
* @return {Array} 返回形状路径数据
*/
instance.getShape = function() {
console.log('getShape()');
return {
edges: edges,
base: {
center: center,
radius: radius
},
polygon: paper.getModelPolygon()
};
};
/**
* 返回当前用户剪辑掉落的形状
*
* @return {Array} 返回形状路径数据
*/
instance.getCutPolygon = function() {
console.log('getCutPolygon()');
return paper.getCutPolygon();
};
/**
* 开始游戏
*/
instance.replay = function() {
console.log('replay()');
status = 'running';
paper.rebuild();
doChange();
};
function doChange() {
render();
if (typeof onchange === 'function') {
onchange.call(instance, {
type: 'change'
});
}
}
/**
* 停止游戏
*/
instance.stop = function() {
console.log('stop()');
if (status === 'stop') {
return;
}
status = 'stop';
};
var freed;
/**
* 释放游戏资源
*/
instance.free = function() {
if (freed) {
return;
}
freed = true;
container.removeEventListener('touchmove', mouseMoveHandler);
container.removeEventListener('touchstart', mouseDownHandler);
container.removeEventListener('touchend', mouseUpHandler);
container.removeEventListener('mousedown', mouseDownHandler);
container.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
paperBackgrund.free();
paperHint.free();
};
function mouseMoveHandler(e) {
if (!downPoint) {
return;
}
var movePoint = e.type === 'mousemove' ?
[e.pageX - this.offsetLeft, e.pageY - this.offsetTop] :
[e.targetTouches[0].pageX - this.offsetLeft, e.targetTouches[0].pageY - this.offsetTop];
points.push(movePoint);
paperHint.attr({
path: jcuts.format('M #{from} L #{lines}', {
from: points[0],
lines: points.slice(1)
})
});
}
function mouseDownHandler(e) {
if (status !== 'running') {
return;
}
downPoint = e.type === 'mousedown' ?
[e.pageX - this.offsetLeft, e.pageY - this.offsetTop] :
[e.targetTouches[0].pageX - this.offsetLeft, e.targetTouches[0].pageY - this.offsetTop];
points = [downPoint];
}
function mouseUpHandler() {
if (!downPoint) {
return;
}
downPoint = null;
paperHint.attr({
path: ''
});
if (status !== 'running') {
return;
}
var ok = paper.cu