开始之前
在本周进行开发时,我们遇到了非常有意思的一个需求。
对文章进行层次化处理,需要把字按照词、句子、段落、文章、书籍的顺序组合起来。每个字都有一个多边形,因此词、句子、段落、文章、书籍就需要把这些多边形组合起来显示。如何写这个算法是重中之重。
设计思路
多边形的存储方式是一个有序点数组。例如:
[[0, 0], [0, 1], [1, 1], [1, 0]]
如果这时又来了一个多边形,要和上面组合呢?
[[0, 0], [0, 1], [-1, 1], [-1, 0]]
我们需要剔除公共边:
[[0, 0], [0, 1], [1, 1], [1, 0]]
[[0, 0], [0, 1], [-1, 1], [-1, 0]]
然后将其组合起来。
[[-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [0, 0]]
你发现了什么?
- 由于第二个多边形的记录方向和第一个相反,其中一个多边形数组被反转了。
- 从公共边开始,将多边形数组拆分开,再匹配端点,连起来。
感觉很简单,不是嘛?但是你还需要考虑这些部分:
- 多边形可能是重叠的。
- 一条公共边可能包含多个点。
- 两个复杂多边形可能有一条以上的公共边,这会导致中间出现空洞。
- 在这个项目中,多边形可能产生跨页。
笔者在这里实现了一个算法,当然它也是有缺陷的,在两个复杂多边形有一条以上的公共边时,我会简单的把中间的空洞删除(因为我的引擎渲染器为了省事,没有给出画洞的功能,哈哈哈。)有兴趣的同学还可以多多探讨。
这里先用BFS标记出所有能融合在一起的多边形组合,然后依次对多边形组进行融合操作。
// --------------------------------------------------------------------------------
PolygonGroup.prototype.isLine = function(dot1, dot2, points){
let a = points.indexOf(dot1);
let b = points.indexOf(dot2);
if(a < 0 || b < 0) return false;
if((a === 0 && b === points.length - 1) || (a === points.length - 1 && b === 0)) return true;
return Math.abs(a - b) === 1;
}
PolygonGroup.prototype.hasCommonLine = function(points_1, points_2){
let a, b;
for(let i = 0; i < points_1.length; i++){
if (i === points_1.length - 1) {
a = i; b = 0;
}else{
a = i; b = i + 1;
}
if(this.isLine(points_1[a], points_1[b], points_2)){
return true;
}
}
return false;
}
PolygonGroup.prototype.getGroupBFS = function(map){
let visited = new Array(map.length);
for(let i = 0; i < visited.length; i++) {
visited[i] = false;
}
let queue = [];
let polygons = [];
for(let i = 0; i < visited.length; i++){
if(!visited[i]){
polygons.push([]);
queue.push(i);
visited[i] = true;
while(queue.length !== 0) {
let target = queue.shift();
polygons[polygons.length - 1].push(target);
for (let j = 0; j < map[target].length; j++) {
if (map[target][j] && !visited[j]) {
queue.push(j);
visited[j] = true;
}
}
}
}
}
return polygons;
}
PolygonGroup.prototype.mergePolygon = function(polygon_1, polygon_2){
let common_dots = [];
let start_dot = null;
for(let i = 0; i < polygon_1.length; i++) {
if(polygon_2.indexOf(polygon_1[i]) !== -1){
common_dots.push(polygon_1[i]);
}else{
start_dot = polygon_1[i];
}
}
let polygon = [start_dot];
let index = polygon_1.indexOf(start_dot);
let now = 1;
let scan = 1;
let target;
while(true){
index += scan;
if(now === 1) {
if(index < 0) index = polygon_1.length - 1;
if(index > polygon_1.length - 1) index = 0;
target = polygon_1[index];
if(target === start_dot) break;
if(common_dots.indexOf(target) === -1){
polygon.push(target);
}else{
polygon.push(target);
now = 2;
index = polygon_2.indexOf(target);
scan = common_dots.indexOf(polygon_2[((index + 1) % polygon_2.length)]) === -1 ? 1 : -1;
}
}else if(now === 2) {
if(index < 0) index = polygon_2.length - 1;
if(index > polygon_2.length - 1) index = 0;
target = polygon_2[index];
if(target === start_dot) break;
if(common_dots.indexOf(target) === -1){
polygon.push(target);
}else{
polygon.push(target);
now = 1;
index = polygon_1.indexOf(target);
scan = common_dots.indexOf(polygon_1[((index + 1) % polygon_1.length)]) === -1 ? 1 : -1;
}
}
}
return polygon;
}
PolygonGroup.prototype.mergePoints = function(points){
let polygon_map = [];
for(let i = 0; i < points.length; i++) {
polygon_map.push(new Array(points.length));
}
for(let i = 0; i < polygon_map.length; i++) {
for (let j = 0; j < polygon_map[i].length; j++) {
polygon_map[i][j] = false;
}
}
for(let i = 0; i < points.length; i++) {
for (let j = i + 1; j < points.length; j++) {
if (this.hasCommonLine(points[i], points[j])){
polygon_map[i][j] = true;
polygon_map[j][i] = true;
}
}
}
let polygon_group = this.getGroupBFS(polygon_map);
let points_group = [];
for(let i = 0; i < polygon_group.length; i++){
points_group.push(points[polygon_group[i].splice(0, 1)]);
while(polygon_group[i].length > 0){
for(let j = 0; j < polygon_group[i].length; j++){
if(this.hasCommonLine(points_group[i], points[polygon_group[i][j]])){
points_group[i] = this.mergePolygon(points_group[i], points[polygon_group[i].splice(j, 1)]);
break;
}
}
}
}
return points_group;
};