Doodle3D-Slicer/src/sliceActions/helpers/comb.js

196 lines
5.3 KiB
JavaScript

import { angle, subtract, distanceTo, normal } from './vector2.js';
const graphs = new WeakMap();
export default function comb(polygons, start, end) {
if (!graphs.has(polygons)) graphs.set(polygons, createGraph(polygons));
let { edges, graph, points, normals } = graphs.get(polygons);
points = [...points, start, end];
graph = [...graph];
const startNode = createNode(graph, points, edges, normals, start);
const endNode = createNode(graph, points, edges, normals, end);
let result;
if (graph[startNode].some(node => node.to === endNode)) {
result = [start, end];
} else {
const path = shortestPath(graph, startNode, endNode);
if (path) {
result = path.map(index => points[index]);
} else {
result = [start, end];
}
}
return result;
}
function createGraph(polygons) {
const points = [];
const edges = [];
const nextPoints = new WeakMap();
const previousPoints = new WeakMap();
const normals = new WeakMap();
for (let i = 0; i < polygons.length; i ++) {
const polygon = polygons[i];
for (let j = 0; j < polygon.length; j ++) {
const point = polygon[j];
const nextPoint = polygon[(j + 1) % polygon.length];
const previousPoint = polygon[(j - 1 + polygon.length) % polygon.length];
points.push(point);
edges.push([point, nextPoint]);
nextPoints.set(point, nextPoint);
previousPoints.set(point, previousPoint);
normals.set(point, normal(subtract(nextPoint, point)));
}
}
const graph = points.map(() => ([]));
for (let i = 0; i < points.length; i ++) {
const a = points[i];
for (let j = i + 1; j < points.length; j ++) {
const b = points[j];
const nextPoint = nextPoints.get(a);
const previousPoint = previousPoints.get(a);
if (lineIsVisible(previousPoint, nextPoint, edges, a, b)) {
const distance = distanceTo(a, b);
const connectNodeA = graph[i];
connectNodeA.push({ to: j, distance });
const connectNodeB = graph[j];
connectNodeB.push({ to: i, distance });
}
}
}
return { graph, edges, points, normals };
}
function createNode(graph, points, edges, normals, point) {
const node = [];
const to = graph.length;
graph.push(node);
let previousPoint;
let nextPoint;
for (let j = 0; j < edges.length; j ++) {
const edge = edges[j];
if (pointOnLine(edge, point)) [previousPoint, nextPoint] = edge;
}
for (let i = 0; i < graph.length; i ++) {
const b = points[i];
if (!lineIsVisible(previousPoint, nextPoint, edges, point, b)) continue;
const distance = distanceTo(point, b);
node.push({ to: i, distance });
graph[i] = [...graph[i], { to, distance }];
}
return to;
}
function lineIsVisible(previousPoint, nextPoint, edges, a, b) {
if (b === nextPoint || b === previousPoint) return true;
if (previousPoint && nextPoint) {
const angleLine = angle(subtract(b, a));
const anglePrevious = angle(subtract(previousPoint, a));
const angleNext = angle(subtract(nextPoint, a));
if (betweenAngles(angleLine, anglePrevious, angleNext)) return false;
}
if (lineCrossesEdges(edges, a, b)) return false;
return true;
}
function lineCrossesEdges(edges, a, b) {
for (let i = 0; i < edges.length; i ++) {
const [c, d] = edges[i];
if (lineSegmentsCross(a, b, c, d)) return true;
}
return false;
}
function lineSegmentsCross(a, b, c, d) {
const denominator = ((b.x - a.x) * (d.y - c.y)) - ((b.y - a.y) * (d.x - c.x));
if (denominator === 0.0) return false;
const numerator1 = ((a.y - c.y) * (d.x - c.x)) - ((a.x - c.x) * (d.y - c.y));
const numerator2 = ((a.y - c.y) * (b.x - a.x)) - ((a.x - c.x) * (b.y - a.y));
if (numerator1 === 0.0 || numerator2 === 0.0) return false;
const r = numerator1 / denominator;
const s = numerator2 / denominator;
return (r > 0.0 && r < 1.0) && (s >= 0.0 && s <= 1.0);
}
function normalizeAngle(a) {
a %= Math.PI * 2;
return a > 0.0 ? a : a + Math.PI * 2;
}
function betweenAngles(n, a, b) {
n = normalizeAngle(n);
a = normalizeAngle(a);
b = normalizeAngle(b);
return a < b ? a <= n && n <= b : a <= n || n <= b;
}
// dijkstra's algorithm
function shortestPath(graph, start, end) {
const distances = graph.map(() => Infinity);
distances[start] = 0;
const traverse = [];
const queue = graph.map((node, i) => i);
while (queue.length > 0) {
let queueIndex;
let minDistance = Infinity;
for (let index = 0; index < queue.length; index ++) {
const nodeIndex = queue[index];
const distance = distances[nodeIndex];
if (distances[nodeIndex] < minDistance) {
queueIndex = index;
minDistance = distance;
}
}
const [nodeIndex] = queue.splice(queueIndex, 1);
const node = graph[nodeIndex];
for (let i = 0; i < node.length; i ++) {
const child = node[i];
const distance = distances[nodeIndex] + child.distance;
if (distance < distances[child.to]) {
distances[child.to] = distance;
traverse[child.to] = nodeIndex;
}
}
}
if (!traverse.hasOwnProperty(end)) return null;
const path = [end];
let nodeIndex = end;
do {
nodeIndex = traverse[nodeIndex];
path.push(nodeIndex);
} while (nodeIndex !== start);
return path.reverse();
}
function pointOnLine([a, b], point) {
return (a.x - point.x) * (a.y - point.y) === (b.x - point.x) * (b.y - point.y);
}