思路基于此文档
export function extract_cycles(vertices, edges) {
let cycles = [];
while (vertices.length > 0) {
let v = leftBottomVertex(vertices),
walk = reduceWalk(closedWalkFrom(v));
if (walk.length > 2) cycles.push(walk);
removeEdge(walk[0], walk[1]);
vertices = removeFilamentAt(walk[0], vertices);
vertices = removeFilamentAt(walk[1], vertices);
};
return cycles;
};
function leftBottomVertex(vertices) {
return vertices.reduce( (m,v) => {
let dx = v.x - m.x;
if (dx < 0) return v;
if (dx > 0) return m;
return v.y - m.y < 0 ? m : v;
} );
}
function closedWalkFrom(v) {
let walk = [], curr = v, prev;
do {
walk.push(curr);
[curr, prev] = getNext(curr, prev);
} while (curr !== v);
return walk;
}
function reduceWalk(w) {
for (let i=1; i < w.length; i++) {
let idup = w.lastIndexOf(w[i]);
if (idup > i) w.splice(i+1, idup - i);
}
return w;
}
function withoutVertex(v, vertices) {
return vertices.filter(vi => vi !== v);
}
function removeEdge(v1, v2) {
v1.adj = withoutVertex(v2, v1.adj);
v2.adj = withoutVertex(v1, v2.adj);
}
function removeFilamentAt(v, vertices) {
let current = v, next;
while (current && current.adj.length < 2) {
vertices = withoutVertex(current, vertices);
next = current.adj[0];
if (next) removeEdge(current, next);
current = next;
}
return vertices;
}
function getNext(v, prev) {
let next = (v.adj.length == 1) ? v.adj[0] : best_by_kind(prev, v, prev ? 'ccw' : 'cw');
return [next, v];
}
function best_by_kind(v_prev, v_curr, kind) {
let d_curr, adj = v_curr.adj;
if (v_prev) {
d_curr = vsub(v_curr, v_prev);
adj = withoutVertex(v_prev, adj);
} else {
d_curr = {x: 0, y: -1};
}
return adj.reduce((v_so_far, v) => better_by_kind(v, v_so_far, v_curr, d_curr, kind));
};
function better_by_kind(v, v_so_far, v_curr, d_curr, kind) {
let d = vsub(v, v_curr),
d_so_far = vsub(v_so_far, v_curr),
is_convex = dot_perp(d_so_far, d_curr) > 0,
curr2v = dot_perp(d_curr, d),
vsf2v = dot_perp(d_so_far, d),
v_is_better;
if (kind == 'cw') {
v_is_better = (is_convex && (curr2v >= 0 || vsf2v >= 0)) || (!is_convex && curr2v >= 0 && vsf2v >= 0);
} else {
v_is_better = (!is_convex && (curr2v < 0 || vsf2v < 0)) || (is_convex && curr2v < 0 && vsf2v < 0);
}
return v_is_better ? v : v_so_far;
};
function vsub(a, b) {
return {x: a.x - b.x, y: a.y - b.y};
}
function dot_perp(a, b) {
return a.x * b.y - b.x * a.y;
}
调用测试
vertices = [{x: 0, y: 0, adj: []}, {x: 5, y: 5, adj: []}, {x: 5, y: 0, adj: []}];
// edges just define adjacent vertices for each vertex
[ [0,1], [1,2], [2,0] ].forEach( (i1,i2) => {
let v1 = vertices[i1], v2 = vertices[i2];
v1.adj.push(v2);
v2.adj.push(v1);
});
let result = extract_cycles(vertices);
# cycles => [];
# filaments => [ [[1,2], [5,6]] ];
# result => [{x: 0, y: 0, ...}, {x: 5, y: 5, ...}, {x: 5, y: 0, ...}]
最终实现需求效果