From 79e4acd3d197e1b198ae4e7d2332887bde00ed6d Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Wed, 2 May 2018 15:07:03 +0200 Subject: [PATCH 01/17] add demo --- comb.js | 336 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 comb.js diff --git a/comb.js b/comb.js new file mode 100644 index 0000000..7d6f299 --- /dev/null +++ b/comb.js @@ -0,0 +1,336 @@ +import earcut from 'earcut'; +import { add, divide, distanceTo, normalize, subtract, normal, dot } from './src/sliceActions/helpers/vector2.js'; + +function lineIntersection(a1, a2, b1, b2) { + // source: http://mathworld.wolfram.com/Line-LineIntersection.html + const intersection = { + x: ((a1.x * a2.y - a1.y * a2.x) * (b1.x - b2.x) - (a1.x - a2.x) * (b1.x * b2.y - b1.y * b2.x)) / ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)), + y: ((a1.x * a2.y - a1.y * a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x * b2.y - b1.y * b2.x)) / ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)) + }; + + const intersectionA = subtract(intersection, a1); + const directionA = subtract(a2, a1); + const normalA = normalize(directionA); + const distanceA = dot(normalA, intersectionA); + if (distanceA < 0 || distanceA > dot(normalA, directionA)) return false; + + const intersectionB = subtract(intersection, b1); + const directionB = subtract(b2, b1); + const normalB = normalize(directionB); + const distanceB = dot(normalB, intersectionB); + if (distanceB < 0 || distanceB > dot(normalB, directionB)) return false; + + return intersection; +} + +function pointIsInsideConvex(point, convex, vertices) { + for (let i = 0; i < convex.length; i ++) { + const vertexA = vertices[convex[i]]; + const vertexB = vertices[convex[(i + 1) % convex.length]]; + + const n = normalize(normal(subtract(vertexB, vertexA))); + const p = subtract(point, vertexA); + + if (dot(p, n) < 0) return false; + } + return true; +} + +function decompose(polygon) { + const vertices = polygon.reduce((points, path) => { + points.push(...path); + return points; + }, []); + const flatVertices = vertices.reduce((points, { x, y }) => { + points.push(x, y); + return points; + }, []); + let offset = 0; + const holes = polygon + .map(path => offset += path.length) + .slice(0, -1); + + const flatTrainglesIndexed = earcut(flatVertices, holes); + const convexPolygons = []; + for (let i = 0; i < flatTrainglesIndexed.length; i += 3) { + const face = [ + flatTrainglesIndexed[i], + flatTrainglesIndexed[i + 1], + flatTrainglesIndexed[i + 2] + ]; + const center = divide(face.reduce((total, point) => { + if (!total) { + return vertices[point]; + } else { + return add(total, vertices[point]); + } + }, null), face.length); + convexPolygons.push({ + center, + face, + connects: [] + }); + } + + for (let i = 0; i < convexPolygons.length; i ++) { + for (let j = i + 1; j < convexPolygons.length; j ++) { + const triangleIndexedA = convexPolygons[i]; + const triangleIndexedB = convexPolygons[j]; + + const overlap = []; + triangleIndexedA.face.map(index => { + if (triangleIndexedB.face.includes(index)) overlap.push(index); + }); + + if (overlap.length === 2) { + const distance = distanceTo(convexPolygons[i].center, convexPolygons[j].center); + triangleIndexedA.connects.push({ to: j, edge: overlap, distance }); + triangleIndexedB.connects.push({ to: i, edge: overlap, distance }); + } + } + } + + return { vertices, convexPolygons }; +} + +function findClosestPath(convexPolygons, start, end, visited = [], path = []) { + if (start === end) return []; + + visited = [...visited, start]; + + const { connects } = convexPolygons[start]; + + const finish = connects.find(({ to }) => to === end); + if (finish) return [...path, finish]; + + const posibilities = []; + for (const connect of connects) { + if (visited.includes(connect.to)) continue; + + const posibility = findClosestPath(convexPolygons, connect.to, end, visited, [...path, connect]); + if (posibility) posibilities.push(posibility); + } + + if (posibilities.length === 0) { + return null; + } else if (posibilities.length === 1) { + return posibilities[0]; + } else if (posibilities.length > 1) { + const distanceMap = new WeakMap(); + for (const posibility of posibilities) { + const distance = posibility.reduce((totalDistance, connect) => totalDistance + connect.distance, 0); + distanceMap.set(posibility, distance); + } + + return posibilities.sort((a, b) => distanceMap.get(a) - distanceMap.get(b))[0]; + } +} + +// const parse = string => parseFloat(string); +// function findClosestPath(map, start, end) { +// // dijkstra's algorithm +// const costs = { [start]: 0 }; +// const open = { [0]: [start] }; +// const predecessors = {}; +// +// while (open) { +// const keys = Object.keys(open).map(parse); +// if (keys.length === 0) break; +// keys.sort(); +// +// const [key] = keys; +// const bucket = open[key]; +// const node = bucket.shift(); +// const currentCost = key; +// const { connects } = map[node]; +// +// if (!bucket.length) delete open[key]; +// +// for (const { distance, to } of connects) { +// const totalCost = distance + currentCost; +// const vertexCost = costs[to]; +// +// if ((typeof vertexCost === 'undefined') || (vertexCost > totalCost)) { +// costs[to] = totalCost; +// +// if (!open[totalCost]) open[totalCost] = []; +// open[totalCost].push(to); +// +// predecessors[to] = node; +// } +// } +// } +// +// if (typeof costs[end] === 'undefined') return null; +// +// const nodes = []; +// let node = end; +// while (typeof node !== 'undefined') { +// nodes.push(node); +// node = predecessors[node]; +// } +// nodes.reverse(); +// +// const path = []; +// for (let i = 1; i < nodes.length; i ++) { +// const from = nodes[i - 1]; +// const to = nodes[i]; +// +// const connection = map[from].connects.find(connect => connect.to === to); +// path.push(connection); +// } +// +// return path; +// } + +function containLineInPath(path, start, end, vertices) { + const line = [start]; + + for (const { edge: [indexA, indexB] } of path) { + const vertexA = vertices[indexA]; + const vertexB = vertices[indexB]; + + const intersection = lineIntersection(start, end, vertexA, vertexB); + if (!intersection) { + const lastPoint = line[line.length - 1]; + const distanceA = distanceTo(lastPoint, vertexA) + distanceTo(vertexA, end); + const distanceB = distanceTo(lastPoint, vertexB) + distanceTo(vertexB, end); + + line.push(distanceA < distanceB ? vertexA : vertexB); + } + } + + line.push(end); + return line; +} + +const canvas = document.createElement('canvas'); +document.body.appendChild(canvas); +canvas.width = 610; +canvas.height = 610; +const context = canvas.getContext('2d'); +context.lineJoin = 'bevel'; + +function circle(radius = 10, x = 0, y = 0, clockWise = true, segments = 40) { + const shape = []; + + for (let rad = 0; rad < Math.PI * 2; rad += Math.PI * 2 / segments) { + if (clockWise) { + shape.push({ x: Math.cos(rad) * radius + x, y: Math.sin(rad) * radius + y }); + } else { + shape.push({ x: Math.cos(rad) * radius + x, y: -Math.sin(rad) * radius + y }); + } + } + + return shape; +} + +const START = { x: 300, y: 300 }; +const END = { x: 300, y: 20 }; +// const CONCAVE_POLYGON = [[ +// { x: 10, y: 10 }, +// { x: 600, y: 10 }, +// { x: 500, y: 200 }, +// { x: 600, y: 600 }, +// { x: 10, y: 600 } +// ], [ +// { x: 160, y: 120 }, +// { x: 120, y: 400 }, +// { x: 400, y: 400 } +// ], circle(50, 300, 100, false)]; +const CONCAVE_POLYGON = [circle(300, 305, 305, true, 100), circle(50, 300, 100, false)]; + +canvas.onmousedown = (event) => { + START.x = event.offsetX; + START.y = event.offsetY; + compute(); +}; +canvas.onmousemove = (event) => { + END.x = event.offsetX; + END.y = event.offsetY; + compute(); +}; +compute(); + +function compute() { + const { convexPolygons, vertices } = decompose(CONCAVE_POLYGON); + const startPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(START, face, vertices)); + const endPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(END, face, vertices)); + if (startPolygon === -1 || endPolygon === -1) return; + + const path = findClosestPath(convexPolygons, startPolygon, endPolygon); + if (!path) return; + const line = containLineInPath(path, START, END, vertices); + + // draw + context.clearRect(0, 0, canvas.width, canvas.height); + + context.beginPath(); + for (const shape of CONCAVE_POLYGON) { + let first = true; + for (const { x, y } of shape) { + if (first) { + context.moveTo(x, y); + } else { + context.lineTo(x, y); + } + first = false; + } + } + context.closePath(); + context.fillStyle = 'lightgray'; + context.fill(); + + // context.fillStyle = 'black'; + // context.strokeStyle = 'black'; + // context.textAlign = 'center'; + // context.textBaseline = 'middle'; + // context.lineWidth = 1; + // context.font = '14px arial'; + // for (let i = 0; i < convexPolygons.length; i ++) { + // const { face, center } = convexPolygons[i]; + // + // context.beginPath(); + // for (const index of face) { + // const vertex = vertices[index]; + // context.lineTo(vertex.x, vertex.y); + // } + // context.closePath(); + // context.stroke(); + // + // context.fillText(i, center.x, center.y); + // } + + // if (path) { + // context.beginPath(); + // for (const { edge: [indexA, indexB] } of path) { + // const pointA = vertices[indexA]; + // const pointB = vertices[indexB]; + // context.moveTo(pointA.x, pointA.y); + // context.lineTo(pointB.x, pointB.y); + // } + // context.strokeStyle = 'blue'; + // context.lineWidth = 3; + // context.stroke(); + // } + + if (line) { + context.beginPath(); + for (const point of line) { + context.lineTo(point.x, point.y); + } + context.strokeStyle = 'green'; + context.lineWidth = 2; + context.stroke(); + } + + context.beginPath(); + context.arc(START.x, START.y, 3, 0, Math.PI * 2); + context.fillStyle = 'blue'; + context.fill(); + + context.beginPath(); + context.arc(END.x, END.y, 3, 0, Math.PI * 2); + context.fillStyle = 'red'; + context.fill(); +} From 6c8b8e9d4493676c5742ceab9239542632d3908e Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Wed, 2 May 2018 15:12:45 +0200 Subject: [PATCH 02/17] implement new combing --- package-lock.json | 61 +++--- package.json | 1 + src/sliceActions/helpers/GCode.js | 8 +- src/sliceActions/helpers/comb.js | 312 +++++++++++++++++++----------- src/sliceActions/slice.js | 4 +- src/sliceActions/slicesToGCode.js | 7 +- 6 files changed, 247 insertions(+), 146 deletions(-) diff --git a/package-lock.json b/package-lock.json index b2fcd31..6a3194e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,6 +90,9 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, + "three-js-csg": { + "version": "github:Doodle3D/three-js-csg#a36f23da6e9be2405a9094de5709cb0ae8f58045" } } }, @@ -3229,6 +3232,11 @@ } } }, + "earcut": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.3.tgz", + "integrity": "sha512-AxdCdWUk1zzK/NuZ7e1ljj6IGC+VAdC3Qb7QQDsXpfNrc5IM8tL9nNXUmEGE6jRHTfZ10zhzRhtDmWVsR5pd3A==" + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -7266,9 +7274,9 @@ "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" }, "lodash-es": { - "version": "4.17.8", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.8.tgz", - "integrity": "sha512-I9mjAxengFAleSThFhhAhvba6fsO0hunb9/0sQ6qQihSZsJRBofv2rYH58WXaOb/O++eUmYpCLywSQ22GfU+sA==" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz", + "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==" }, "lodash._arraycopy": { "version": "3.0.0", @@ -8254,9 +8262,9 @@ } }, "node-abi": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz", - "integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.0.tgz", + "integrity": "sha512-hRUz0vG+eJfSqwU6rOgW6wNyX85ec8OEE9n4A+u+eoiE8oTePhCkUFTNmwQ+86Kyu429PCLNNyI2P2jL9qKXhw==", "requires": { "semver": "5.4.1" } @@ -9344,14 +9352,14 @@ "github-from-package": "0.0.0", "minimist": "1.2.0", "mkdirp": "0.5.1", - "node-abi": "2.3.0", + "node-abi": "2.4.0", "noop-logger": "0.1.1", "npmlog": "4.1.2", "os-homedir": "1.0.2", "pump": "2.0.1", "rc": "1.2.6", - "simple-get": "2.7.0", - "tar-fs": "1.16.0", + "simple-get": "2.8.1", + "tar-fs": "1.16.2", "tunnel-agent": "0.6.0", "which-pm-runs": "1.0.0" }, @@ -9748,8 +9756,8 @@ "requires": { "hoist-non-react-statics": "2.5.0", "invariant": "2.2.2", - "lodash": "4.17.5", - "lodash-es": "4.17.8", + "lodash": "4.17.10", + "lodash-es": "4.17.10", "loose-envify": "1.3.1", "prop-types": "15.6.0" }, @@ -9760,9 +9768,9 @@ "integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w==" }, "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" } } }, @@ -9921,8 +9929,8 @@ "hoist-non-react-statics": "2.5.0", "invariant": "2.2.4", "is-promise": "2.1.0", - "lodash": "4.17.5", - "lodash-es": "4.17.8", + "lodash": "4.17.10", + "lodash-es": "4.17.10", "prop-types": "15.6.1" }, "dependencies": { @@ -9940,9 +9948,9 @@ } }, "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, "prop-types": { "version": "15.6.1", @@ -10462,9 +10470,9 @@ "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" }, "simple-get": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz", - "integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", + "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", "requires": { "decompress-response": "3.3.0", "once": "1.4.0", @@ -10906,9 +10914,9 @@ "dev": true }, "tar-fs": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.0.tgz", - "integrity": "sha512-I9rb6v7mjWLtOfCau9eH5L7sLJyU2BnxtEZRQ5Mt+eRKmf1F0ohXmT/Jc3fr52kDvjJ/HV5MH3soQfPL5bQ0Yg==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.2.tgz", + "integrity": "sha512-LdknWjPEiZC1nOBwhv0JBzfJBGPJar08dZg2rwZe0ZTLQoRGEzgrl7vF3qUEkCHpI/wN9e7RyCuDhMsJUCLPPQ==", "requires": { "chownr": "1.0.1", "mkdirp": "0.5.1", @@ -10999,9 +11007,6 @@ "resolved": "https://registry.npmjs.org/three/-/three-0.88.0.tgz", "integrity": "sha1-QlbC/Djk+yOg0j66K2zOTfjkZtU=" }, - "three-js-csg": { - "version": "github:Doodle3D/three-js-csg#a36f23da6e9be2405a9094de5709cb0ae8f58045" - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", diff --git a/package.json b/package.json index e045faf..fbf094a 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@doodle3d/doodle3d-api": "^1.0.5", "@doodle3d/doodle3d-core": "github:doodle3d/doodle3d-core", "babel-plugin-transform-class-properties": "^6.24.1", + "earcut": "^2.1.3", "file-saver": "^1.3.3", "lodash": "^4.17.4", "material-ui": "^0.19.4", diff --git a/src/sliceActions/helpers/GCode.js b/src/sliceActions/helpers/GCode.js index 65ac5bb..0f01e99 100644 --- a/src/sliceActions/helpers/GCode.js +++ b/src/sliceActions/helpers/GCode.js @@ -1,5 +1,5 @@ -import { scale, distanceTo } from './vector2.js'; -import { PRECISION, VERSION } from '../../constants.js'; +import { distanceTo } from './vector2.js'; +import { VERSION } from '../../constants.js'; export const MOVE = 'G'; export const M_COMMAND = 'M'; @@ -55,7 +55,7 @@ export default class GCode { } moveTo(x, y, z, { speed }) { - const newNozzlePosition = scale({ x, y }, PRECISION); + const newNozzlePosition = { x, y }; const lineLength = distanceTo(this._nozzlePosition, newNozzlePosition); this._duration += lineLength / speed; @@ -74,7 +74,7 @@ export default class GCode { } lineTo(x, y, z, { speed, flowRate }) { - const newNozzlePosition = scale({ x, y }, PRECISION); + const newNozzlePosition = { x, y }; const lineLength = distanceTo(this._nozzlePosition, newNozzlePosition); this._extruder += this._nozzleToFilamentRatio * lineLength * flowRate; diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index 538cda9..76f07dd 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -1,131 +1,225 @@ -import Shape from 'clipper-js'; -import { subtract, add, scale, normalize, dot, length, distanceTo } from './vector2.js'; -import { PRECISION } from '../../constants.js'; +import { subtract, add, normalize, dot, distanceTo, divide, normal } from './vector2.js'; +import earcut from 'earcut'; -const TOLERANCE = 1 / PRECISION; +// const TRIANGULATED_OUTLINES = new WeakMap(); export default function comb(outline, start, end) { - if (distanceTo(start, end) < TOLERANCE) { - return [start, end]; + if (distanceTo(start, end) < 10) return [start, end]; + + // if (!TRIANGULATED_OUTLINES.has(outline)) TRIANGULATED_OUTLINES.set(outline, decompose(outline)); + // const { convexPolygons, vertices } = TRIANGULATED_OUTLINES.get(outline); + const { convexPolygons, vertices } = decompose(outline); + const startPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(start, face, vertices)); + const endPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(end, face, vertices)); + if (startPolygon === -1 || endPolygon === -1) return [start, end]; + if (startPolygon === endPolygon) return [start, end]; + + const path = findClosestPath(convexPolygons, startPolygon, endPolygon); + if (!path) return [start, end]; + const line = containLineInPath(path, start, end, vertices); + + return line; +} + +function lineIntersection(a1, a2, b1, b2) { + // source: http://mathworld.wolfram.com/Line-LineIntersection.html + const intersection = { + x: ((a1.x * a2.y - a1.y * a2.x) * (b1.x - b2.x) - (a1.x - a2.x) * (b1.x * b2.y - b1.y * b2.x)) / ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)), + y: ((a1.x * a2.y - a1.y * a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x * b2.y - b1.y * b2.x)) / ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)) + }; + + const intersectionA = subtract(intersection, a1); + const directionA = subtract(a2, a1); + const normalA = normalize(directionA); + const distanceA = dot(normalA, intersectionA); + if (distanceA < 0 || distanceA > dot(normalA, directionA)) return false; + + const intersectionB = subtract(intersection, b1); + const directionB = subtract(b2, b1); + const normalB = normalize(directionB); + const distanceB = dot(normalB, intersectionB); + if (distanceB < 0 || distanceB > dot(normalB, directionB)) return false; + + return intersection; +} + +function pointIsInsideConvex(point, convex, vertices) { + for (let i = 0; i < convex.length; i ++) { + const vertexA = vertices[convex[i]]; + const vertexB = vertices[convex[(i + 1) % convex.length]]; + + const n = normalize(normal(subtract(vertexB, vertexA))); + const p = subtract(point, vertexA); + + if (dot(p, n) < 0) return false; + } + return true; +} + +function decompose(polygon) { + const vertices = polygon.reduce((points, path) => { + points.push(...path); + return points; + }, []); + const flatVertices = vertices.reduce((points, { x, y }) => { + points.push(x, y); + return points; + }, []); + let offset = 0; + const holes = polygon + .map(path => offset += path.length) + .slice(0, -1); + + const flatTrainglesIndexed = earcut(flatVertices, holes); + const convexPolygons = []; + for (let i = 0; i < flatTrainglesIndexed.length; i += 3) { + const face = [ + flatTrainglesIndexed[i], + flatTrainglesIndexed[i + 1], + flatTrainglesIndexed[i + 2] + ]; + const center = divide(face.reduce((total, point) => { + if (!total) { + return vertices[point]; + } else { + return add(total, vertices[point]); + } + }, null), face.length); + convexPolygons.push({ + center, + face, + connects: [] + }); } - let combPath = new Shape([[start, end]], false, true, false); + for (let i = 0; i < convexPolygons.length; i ++) { + for (let j = i + 1; j < convexPolygons.length; j ++) { + const triangleIndexedA = convexPolygons[i]; + const triangleIndexedB = convexPolygons[j]; - for (let i = 0; i < outline.paths.length; i ++) { - let outlinePart = new Shape([outline.paths[i]], true, false, false, true); + const overlap = []; + triangleIndexedA.face.map(index => { + if (triangleIndexedB.face.includes(index)) overlap.push(index); + }); - let snappedCombPaths = outlinePart.orientation(0) ? combPath.intersect(outlinePart) : combPath.difference(outlinePart); - - snappedCombPaths = snappedCombPaths.mapToLower(); - outlinePart = outlinePart.mapToLower()[0]; - - if (distanceTo(start, outlinePart[outlinePart.length - 1]) < distanceTo(start, outlinePart[0])) { - outlinePart = outlinePart.reverse(); + if (overlap.length === 2) { + const distance = distanceTo(convexPolygons[i].center, convexPolygons[j].center); + triangleIndexedA.connects.push({ to: j, edge: overlap, distance }); + triangleIndexedB.connects.push({ to: i, edge: overlap, distance }); + } } + } + return { vertices, convexPolygons }; +} + +function findClosestPath(convexPolygons, start, end, visited = [], path = []) { + if (start === end) return []; + + visited = [...visited, start]; + + const { connects } = convexPolygons[start]; + + const finish = connects.find(({ to }) => to === end); + if (finish) return [...path, finish]; + + const posibilities = []; + for (const connect of connects) { + if (visited.includes(connect.to)) continue; + + const posibility = findClosestPath(convexPolygons, connect.to, end, visited, [...path, connect]); + if (posibility) posibilities.push(posibility); + } + + if (posibilities.length === 0) { + return null; + } else if (posibilities.length === 1) { + return posibilities[0]; + } else if (posibilities.length > 1) { const distanceMap = new WeakMap(); - - for (let i = 0; i < snappedCombPaths.length; i ++) { - const snappedCombPath = snappedCombPaths[i]; - - const distanceStart = distanceTo(start, snappedCombPath[0]); - const distanceEnd = distanceTo(start, snappedCombPath[snappedCombPath.length - 1]); - - if (distanceStart < distanceEnd) { - distanceMap.set(snappedCombPath, distanceStart); - } else { - snappedCombPath.reverse(); - distanceMap.set(snappedCombPath, distanceEnd); - } - } - snappedCombPaths.sort((a, b) => distanceMap.get(a) - distanceMap.get(b)); - - const firstPath = snappedCombPaths[0]; - const lastPath = snappedCombPaths[snappedCombPaths.length - 1]; - - if (snappedCombPaths.length === 0) { - snappedCombPaths.push([start], [end]); - } else if (distanceTo(firstPath[0], start) > 1.0) { - snappedCombPaths.unshift([start]); - } else if (distanceTo(lastPath[lastPath.length - 1], end) > 1.0) { - snappedCombPaths.push([end]); + for (const posibility of posibilities) { + const distance = posibility.reduce((totalDistance, connect) => totalDistance + connect.distance, 0); + distanceMap.set(posibility, distance); } - if (snappedCombPaths.length === 1) { - continue; - } - - const startPath = snappedCombPaths[0]; - const startPoint = startPath[startPath.length - 1]; - - const endPath = snappedCombPaths[snappedCombPaths.length - 1]; - const endPoint = endPath[0]; - - const lineIndexStart = findClosestLineOnPath(outlinePart, startPoint); - const lineIndexEnd = findClosestLineOnPath(outlinePart, endPoint); - - const path = []; - if (lineIndexEnd === lineIndexStart) { - continue; - } else if (lineIndexEnd > lineIndexStart) { - if (lineIndexStart + outlinePart.length - lineIndexEnd < lineIndexEnd - lineIndexStart) { - for (let i = lineIndexStart + outlinePart.length; i > lineIndexEnd; i --) { - path.push(outlinePart[i % outlinePart.length]); - } - } else { - for (let i = lineIndexStart; i < lineIndexEnd; i ++) { - path.push(outlinePart[i + 1]); - } - } - } else { - if (lineIndexEnd + outlinePart.length - lineIndexStart < lineIndexStart - lineIndexEnd) { - for (let i = lineIndexStart; i < lineIndexEnd + outlinePart.length; i ++) { - path.push(outlinePart[(i + 1) % outlinePart.length]); - } - } else { - for (let i = lineIndexStart; i > lineIndexEnd; i --) { - path.push(outlinePart[i]); - } - } - } - - combPath = new Shape([[...startPath, ...path, ...endPath]], false, true, false, true); + return posibilities.sort((a, b) => distanceMap.get(a) - distanceMap.get(b))[0]; } - - return combPath.mapToLower()[0]; } -function findClosestLineOnPath(path, point) { - let distance = Infinity; - let lineIndex; +// const parse = string => parseFloat(string); +// function findClosestPath(map, start, end) { +// // dijkstra's algorithm +// const costs = { [start]: 0 }; +// const open = { [0]: [start] }; +// const predecessors = {}; +// +// while (open) { +// const keys = Object.keys(open).map(parse); +// if (keys.length === 0) break; +// keys.sort(); +// +// const [key] = keys; +// const bucket = open[key]; +// const node = bucket.shift(); +// const currentCost = key; +// const { connects } = map[node]; +// +// if (!bucket.length) delete open[key]; +// +// for (const { distance, to } of connects) { +// const totalCost = distance + currentCost; +// const vertexCost = costs[to]; +// +// if ((typeof vertexCost === 'undefined') || (vertexCost > totalCost)) { +// costs[to] = totalCost; +// +// if (!open[totalCost]) open[totalCost] = []; +// open[totalCost].push(to); +// +// predecessors[to] = node; +// } +// } +// } +// +// if (typeof costs[end] === 'undefined') return null; +// +// const nodes = []; +// let node = end; +// while (typeof node !== 'undefined') { +// nodes.push(node); +// node = predecessors[node]; +// } +// nodes.reverse(); +// +// const path = []; +// for (let i = 1; i < nodes.length; i ++) { +// const from = nodes[i - 1]; +// const to = nodes[i]; +// +// const connection = map[from].connects.find(connect => connect.to === to); +// path.push(connection); +// } +// +// return path; +// } - for (let i = 0; i < path.length; i ++) { - const pointA = path[i]; - const pointB = path[(i + 1) % path.length]; +function containLineInPath(path, start, end, vertices) { + const line = [start]; - const tempClosestPoint = findClosestPointOnLine(pointA, pointB, point); - const tempDistance = distanceTo(tempClosestPoint, point); + for (const { edge: [indexA, indexB] } of path) { + const vertexA = vertices[indexA]; + const vertexB = vertices[indexB]; - if (tempDistance < distance) { - distance = tempDistance; - lineIndex = i; + const intersection = lineIntersection(start, end, vertexA, vertexB); + if (!intersection) { + const lastPoint = line[line.length - 1]; + const distanceA = distanceTo(lastPoint, vertexA) + distanceTo(vertexA, end); + const distanceB = distanceTo(lastPoint, vertexB) + distanceTo(vertexB, end); + + line.push(distanceA < distanceB ? vertexA : vertexB); } } - return lineIndex; -} - -function findClosestPointOnLine(a, b, c) { - const b_ = subtract(b, a); - const c_ = subtract(c, a); - - const lambda = dot(normalize(b_), c_) / length(b_); - - if (lambda >= 1) { - return b; - } else if (lambda > 0) { - return add(a, scale(b_, lambda)); - } else { - return a; - } + line.push(end); + return line; } diff --git a/src/sliceActions/slice.js b/src/sliceActions/slice.js index 592a7a5..ab9826f 100644 --- a/src/sliceActions/slice.js +++ b/src/sliceActions/slice.js @@ -11,7 +11,7 @@ import shapesToSlices from './shapesToSlices.js'; import slicesToGCode from './slicesToGCode.js'; import applyPrecision from './applyPrecision.js'; import { hslToRgb } from './helpers/color.js'; -// // import removePrecision from './removePrecision.js'; +import removePrecision from './removePrecision.js'; export default function slice(settings, geometry, openObjectIndexes, constructLinePreview, onProgress) { const total = 11; @@ -48,7 +48,7 @@ export default function slice(settings, geometry, openObjectIndexes, constructLi updateProgress('Optimizing paths'); optimizePaths(slices, settings); - // removePrecision(slices); + removePrecision(slices); updateProgress('Constructing gcode'); const gcode = slicesToGCode(slices, settings); diff --git a/src/sliceActions/slicesToGCode.js b/src/sliceActions/slicesToGCode.js index 19ee59d..c1434e9 100644 --- a/src/sliceActions/slicesToGCode.js +++ b/src/sliceActions/slicesToGCode.js @@ -52,7 +52,7 @@ export default function slicesToGCode(slices, settings) { const part = slice.parts[i]; if (part.closed) { - const outline = part.shell[0]; + const outline = part.shell[0].mapToLower(); for (let i = 0; i < part.shell.length; i ++) { const shell = part.shell[i]; @@ -72,7 +72,8 @@ export default function slicesToGCode(slices, settings) { } if (typeof slice.support !== 'undefined') { - pathToGCode(slice.supportOutline, combing, gcode, slice.support, true, true, z, profiles.support); + const supportOutline = slice.supportOutline.mapToLower(); + pathToGCode(supportOutline, combing, gcode, slice.support, true, true, z, profiles.support); } } @@ -95,7 +96,7 @@ function pathToGCode(outline, combing, gcode, shape, retract, unRetract, z, prof if (i === 0) { if (combing) { - const combPath = comb(outline, divide(gcode._nozzlePosition, PRECISION), point); + const combPath = comb(outline, gcode._nozzlePosition, point); for (let i = 0; i < combPath.length; i ++) { const combPoint = combPath[i]; gcode.moveTo(combPoint.x, combPoint.y, z, travelProfile); From e966bc89b244f7bd8b32d2dec2dfdcad0c0924f4 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Wed, 2 May 2018 16:27:54 +0200 Subject: [PATCH 03/17] performance --- src/sliceActions/helpers/comb.js | 172 ++++++++++++++++--------------- 1 file changed, 87 insertions(+), 85 deletions(-) diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index 76f07dd..b4e1fbd 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -1,14 +1,14 @@ import { subtract, add, normalize, dot, distanceTo, divide, normal } from './vector2.js'; import earcut from 'earcut'; -// const TRIANGULATED_OUTLINES = new WeakMap(); +const TRIANGULATED_OUTLINES = new WeakMap(); export default function comb(outline, start, end) { if (distanceTo(start, end) < 10) return [start, end]; - // if (!TRIANGULATED_OUTLINES.has(outline)) TRIANGULATED_OUTLINES.set(outline, decompose(outline)); - // const { convexPolygons, vertices } = TRIANGULATED_OUTLINES.get(outline); - const { convexPolygons, vertices } = decompose(outline); + if (!TRIANGULATED_OUTLINES.has(outline)) TRIANGULATED_OUTLINES.set(outline, decompose(outline)); + const { convexPolygons, vertices } = TRIANGULATED_OUTLINES.get(outline); + const startPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(start, face, vertices)); const endPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(end, face, vertices)); if (startPolygon === -1 || endPolygon === -1) return [start, end]; @@ -16,8 +16,8 @@ export default function comb(outline, start, end) { const path = findClosestPath(convexPolygons, startPolygon, endPolygon); if (!path) return [start, end]; - const line = containLineInPath(path, start, end, vertices); + const line = containLineInPath(path, start, end, vertices); return line; } @@ -113,100 +113,102 @@ function decompose(polygon) { return { vertices, convexPolygons }; } -function findClosestPath(convexPolygons, start, end, visited = [], path = []) { - if (start === end) return []; - - visited = [...visited, start]; - - const { connects } = convexPolygons[start]; - - const finish = connects.find(({ to }) => to === end); - if (finish) return [...path, finish]; - - const posibilities = []; - for (const connect of connects) { - if (visited.includes(connect.to)) continue; - - const posibility = findClosestPath(convexPolygons, connect.to, end, visited, [...path, connect]); - if (posibility) posibilities.push(posibility); - } - - if (posibilities.length === 0) { - return null; - } else if (posibilities.length === 1) { - return posibilities[0]; - } else if (posibilities.length > 1) { - const distanceMap = new WeakMap(); - for (const posibility of posibilities) { - const distance = posibility.reduce((totalDistance, connect) => totalDistance + connect.distance, 0); - distanceMap.set(posibility, distance); - } - - return posibilities.sort((a, b) => distanceMap.get(a) - distanceMap.get(b))[0]; - } -} - -// const parse = string => parseFloat(string); -// function findClosestPath(map, start, end) { -// // dijkstra's algorithm -// const costs = { [start]: 0 }; -// const open = { [0]: [start] }; -// const predecessors = {}; +// const distanceMap = new WeakMap(); +// function findClosestPath(convexPolygons, start, end, visited = [], path = [], distance = 0) { +// if (start === end) return []; // -// while (open) { -// const keys = Object.keys(open).map(parse); -// if (keys.length === 0) break; -// keys.sort(); +// visited = [...visited, start]; // -// const [key] = keys; -// const bucket = open[key]; -// const node = bucket.shift(); -// const currentCost = key; -// const { connects } = map[node]; +// const { connects } = convexPolygons[start]; // -// if (!bucket.length) delete open[key]; +// const finish = connects.find(({ to }) => to === end); +// if (finish) return [...path, finish]; // -// for (const { distance, to } of connects) { -// const totalCost = distance + currentCost; -// const vertexCost = costs[to]; +// const posibilities = []; +// for (let i = 0; i < connects.length; i ++) { +// const connect = connects[i]; +// if (visited.includes(connect.to)) continue; // -// if ((typeof vertexCost === 'undefined') || (vertexCost > totalCost)) { -// costs[to] = totalCost; -// -// if (!open[totalCost]) open[totalCost] = []; -// open[totalCost].push(to); -// -// predecessors[to] = node; -// } +// const positibiltyDistance = distance + connect.distance; +// const posibility = findClosestPath(convexPolygons, connect.to, end, visited, [...path, connect], positibiltyDistance); +// if (posibility) { +// posibilities.push(posibility); +// distanceMap.set(posibility, positibiltyDistance); // } // } // -// if (typeof costs[end] === 'undefined') return null; -// -// const nodes = []; -// let node = end; -// while (typeof node !== 'undefined') { -// nodes.push(node); -// node = predecessors[node]; +// if (posibilities.length === 0) { +// return null; +// } else if (posibilities.length === 1) { +// return posibilities[0]; +// } else if (posibilities.length > 1) { +// return posibilities.sort((a, b) => distanceMap.get(a) - distanceMap.get(b))[0]; // } -// nodes.reverse(); -// -// const path = []; -// for (let i = 1; i < nodes.length; i ++) { -// const from = nodes[i - 1]; -// const to = nodes[i]; -// -// const connection = map[from].connects.find(connect => connect.to === to); -// path.push(connection); -// } -// -// return path; // } +const findKey = _key => ({ key }) => _key === key; +function findClosestPath(map, start, end) { + // dijkstra's algorithm + const distances = { [start]: 0 }; + const open = [{ key: 0, nodes: [start] }]; + const predecessors = {}; + + while (open.length !== 0) { + const key = Math.min(...open.map(n => n.key).sort()); + const bucket = open.find(findKey(key)); + const node = bucket.nodes.shift(); + const currentDistance = key; + const { connects } = map[node]; + + if (bucket.nodes.length === 0) open.splice(open.indexOf(bucket), 1); + + for (let i = 0; i < connects.length; i ++) { + const { distance, to } = connects[i]; + const totalDistance = distance + currentDistance; + const vertexDistance = distances[to]; + + if ((typeof vertexDistance === 'undefined') || (vertexDistance > totalDistance)) { + distances[to] = totalDistance; + + let openNode = open.find(findKey(totalDistance)); + if (!openNode) { + openNode = { key: totalDistance, nodes: [] }; + open.push(openNode); + } + openNode.nodes.push(to); + + predecessors[to] = node; + } + } + } + + if (typeof distances[end] === 'undefined') return null; + + const nodes = []; + let node = end; + while (typeof node !== 'undefined') { + nodes.push(node); + node = predecessors[node]; + } + nodes.reverse(); + + const path = []; + for (let i = 1; i < nodes.length; i ++) { + const from = nodes[i - 1]; + const to = nodes[i]; + + const connection = map[from].connects.find(connect => connect.to === to); + path.push(connection); + } + + return path; +} + function containLineInPath(path, start, end, vertices) { const line = [start]; - for (const { edge: [indexA, indexB] } of path) { + for (let i = 0; i < path.length; i ++) { + const { edge: [indexA, indexB] } = path[i]; const vertexA = vertices[indexA]; const vertexB = vertices[indexB]; From 0f708559896c131d4bc5042b88ee5c8c0b2037d5 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Wed, 2 May 2018 16:29:44 +0200 Subject: [PATCH 04/17] update example --- comb.js | 226 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 115 insertions(+), 111 deletions(-) diff --git a/comb.js b/comb.js index 7d6f299..da5aa3c 100644 --- a/comb.js +++ b/comb.js @@ -1,6 +1,7 @@ import earcut from 'earcut'; import { add, divide, distanceTo, normalize, subtract, normal, dot } from './src/sliceActions/helpers/vector2.js'; + function lineIntersection(a1, a2, b1, b2) { // source: http://mathworld.wolfram.com/Line-LineIntersection.html const intersection = { @@ -93,100 +94,102 @@ function decompose(polygon) { return { vertices, convexPolygons }; } -function findClosestPath(convexPolygons, start, end, visited = [], path = []) { - if (start === end) return []; - - visited = [...visited, start]; - - const { connects } = convexPolygons[start]; - - const finish = connects.find(({ to }) => to === end); - if (finish) return [...path, finish]; - - const posibilities = []; - for (const connect of connects) { - if (visited.includes(connect.to)) continue; - - const posibility = findClosestPath(convexPolygons, connect.to, end, visited, [...path, connect]); - if (posibility) posibilities.push(posibility); - } - - if (posibilities.length === 0) { - return null; - } else if (posibilities.length === 1) { - return posibilities[0]; - } else if (posibilities.length > 1) { - const distanceMap = new WeakMap(); - for (const posibility of posibilities) { - const distance = posibility.reduce((totalDistance, connect) => totalDistance + connect.distance, 0); - distanceMap.set(posibility, distance); - } - - return posibilities.sort((a, b) => distanceMap.get(a) - distanceMap.get(b))[0]; - } -} - -// const parse = string => parseFloat(string); -// function findClosestPath(map, start, end) { -// // dijkstra's algorithm -// const costs = { [start]: 0 }; -// const open = { [0]: [start] }; -// const predecessors = {}; +// const distanceMap = new WeakMap(); +// function findClosestPath(convexPolygons, start, end, visited = [], path = [], distance = 0) { +// if (start === end) return []; // -// while (open) { -// const keys = Object.keys(open).map(parse); -// if (keys.length === 0) break; -// keys.sort(); +// visited = [...visited, start]; // -// const [key] = keys; -// const bucket = open[key]; -// const node = bucket.shift(); -// const currentCost = key; -// const { connects } = map[node]; +// const { connects } = convexPolygons[start]; // -// if (!bucket.length) delete open[key]; +// const finish = connects.find(({ to }) => to === end); +// if (finish) return [...path, finish]; // -// for (const { distance, to } of connects) { -// const totalCost = distance + currentCost; -// const vertexCost = costs[to]; +// const posibilities = []; +// for (let i = 0; i < connects.length; i ++) { +// const connect = connects[i]; +// if (visited.includes(connect.to)) continue; // -// if ((typeof vertexCost === 'undefined') || (vertexCost > totalCost)) { -// costs[to] = totalCost; -// -// if (!open[totalCost]) open[totalCost] = []; -// open[totalCost].push(to); -// -// predecessors[to] = node; -// } +// const positibiltyDistance = distance + connect.distance; +// const posibility = findClosestPath(convexPolygons, connect.to, end, visited, [...path, connect], positibiltyDistance); +// if (posibility) { +// posibilities.push(posibility); +// distanceMap.set(posibility, positibiltyDistance); // } // } // -// if (typeof costs[end] === 'undefined') return null; -// -// const nodes = []; -// let node = end; -// while (typeof node !== 'undefined') { -// nodes.push(node); -// node = predecessors[node]; +// if (posibilities.length === 0) { +// return null; +// } else if (posibilities.length === 1) { +// return posibilities[0]; +// } else if (posibilities.length > 1) { +// return posibilities.sort((a, b) => distanceMap.get(a) - distanceMap.get(b))[0]; // } -// nodes.reverse(); -// -// const path = []; -// for (let i = 1; i < nodes.length; i ++) { -// const from = nodes[i - 1]; -// const to = nodes[i]; -// -// const connection = map[from].connects.find(connect => connect.to === to); -// path.push(connection); -// } -// -// return path; // } +const findKey = _key => ({ key }) => _key === key; +function findClosestPath(map, start, end) { + // dijkstra's algorithm + const distances = { [start]: 0 }; + const open = [{ key: 0, nodes: [start] }]; + const predecessors = {}; + + while (open.length !== 0) { + const key = Math.min(...open.map(n => n.key).sort()); + const bucket = open.find(findKey(key)); + const node = bucket.nodes.shift(); + const currentDistance = key; + const { connects } = map[node]; + + if (bucket.nodes.length === 0) open.splice(open.indexOf(bucket), 1); + + for (let i = 0; i < connects.length; i ++) { + const { distance, to } = connects[i]; + const totalDistance = distance + currentDistance; + const vertexDistance = distances[to]; + + if ((typeof vertexDistance === 'undefined') || (vertexDistance > totalDistance)) { + distances[to] = totalDistance; + + let openNode = open.find(findKey(totalDistance)); + if (!openNode) { + openNode = { key: totalDistance, nodes: [] }; + open.push(openNode); + } + openNode.nodes.push(to); + + predecessors[to] = node; + } + } + } + + if (typeof distances[end] === 'undefined') return null; + + const nodes = []; + let node = end; + while (typeof node !== 'undefined') { + nodes.push(node); + node = predecessors[node]; + } + nodes.reverse(); + + const path = []; + for (let i = 1; i < nodes.length; i ++) { + const from = nodes[i - 1]; + const to = nodes[i]; + + const connection = map[from].connects.find(connect => connect.to === to); + path.push(connection); + } + + return path; +} + function containLineInPath(path, start, end, vertices) { const line = [start]; - for (const { edge: [indexA, indexB] } of path) { + for (let i = 0; i < path.length; i ++) { + const { edge: [indexA, indexB] } = path[i]; const vertexA = vertices[indexA]; const vertexB = vertices[indexB]; @@ -204,6 +207,7 @@ function containLineInPath(path, start, end, vertices) { return line; } + const canvas = document.createElement('canvas'); document.body.appendChild(canvas); canvas.width = 610; @@ -281,38 +285,38 @@ function compute() { context.fillStyle = 'lightgray'; context.fill(); - // context.fillStyle = 'black'; - // context.strokeStyle = 'black'; - // context.textAlign = 'center'; - // context.textBaseline = 'middle'; - // context.lineWidth = 1; - // context.font = '14px arial'; - // for (let i = 0; i < convexPolygons.length; i ++) { - // const { face, center } = convexPolygons[i]; - // - // context.beginPath(); - // for (const index of face) { - // const vertex = vertices[index]; - // context.lineTo(vertex.x, vertex.y); - // } - // context.closePath(); - // context.stroke(); - // - // context.fillText(i, center.x, center.y); - // } + context.fillStyle = 'black'; + context.strokeStyle = 'black'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.lineWidth = 1; + context.font = '14px arial'; + for (let i = 0; i < convexPolygons.length; i ++) { + const { face, center } = convexPolygons[i]; - // if (path) { - // context.beginPath(); - // for (const { edge: [indexA, indexB] } of path) { - // const pointA = vertices[indexA]; - // const pointB = vertices[indexB]; - // context.moveTo(pointA.x, pointA.y); - // context.lineTo(pointB.x, pointB.y); - // } - // context.strokeStyle = 'blue'; - // context.lineWidth = 3; - // context.stroke(); - // } + context.beginPath(); + for (const index of face) { + const vertex = vertices[index]; + context.lineTo(vertex.x, vertex.y); + } + context.closePath(); + context.stroke(); + + context.fillText(i, center.x, center.y); + } + + if (path) { + context.beginPath(); + for (const { edge: [indexA, indexB] } of path) { + const pointA = vertices[indexA]; + const pointB = vertices[indexB]; + context.moveTo(pointA.x, pointA.y); + context.lineTo(pointB.x, pointB.y); + } + context.strokeStyle = 'blue'; + context.lineWidth = 3; + context.stroke(); + } if (line) { context.beginPath(); From 50ff72a0379a421c419132fa2f3592346f7243cb Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Wed, 2 May 2018 16:54:19 +0200 Subject: [PATCH 05/17] change distance check to 3 --- src/sliceActions/helpers/comb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index b4e1fbd..80e7b41 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -4,7 +4,7 @@ import earcut from 'earcut'; const TRIANGULATED_OUTLINES = new WeakMap(); export default function comb(outline, start, end) { - if (distanceTo(start, end) < 10) return [start, end]; + if (distanceTo(start, end) < 3) return [start, end]; if (!TRIANGULATED_OUTLINES.has(outline)) TRIANGULATED_OUTLINES.set(outline, decompose(outline)); const { convexPolygons, vertices } = TRIANGULATED_OUTLINES.get(outline); From b7269da172f2100e52eaa8bfeaae7e705e9765dd Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Wed, 2 May 2018 17:18:48 +0200 Subject: [PATCH 06/17] update example --- comb.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/comb.js b/comb.js index da5aa3c..27ec53a 100644 --- a/comb.js +++ b/comb.js @@ -1,7 +1,6 @@ import earcut from 'earcut'; import { add, divide, distanceTo, normalize, subtract, normal, dot } from './src/sliceActions/helpers/vector2.js'; - function lineIntersection(a1, a2, b1, b2) { // source: http://mathworld.wolfram.com/Line-LineIntersection.html const intersection = { @@ -229,7 +228,7 @@ function circle(radius = 10, x = 0, y = 0, clockWise = true, segments = 40) { return shape; } -const START = { x: 300, y: 300 }; +const START = { x: 300, y: 40 }; const END = { x: 300, y: 20 }; // const CONCAVE_POLYGON = [[ // { x: 10, y: 10 }, @@ -242,7 +241,14 @@ const END = { x: 300, y: 20 }; // { x: 120, y: 400 }, // { x: 400, y: 400 } // ], circle(50, 300, 100, false)]; -const CONCAVE_POLYGON = [circle(300, 305, 305, true, 100), circle(50, 300, 100, false)]; +const CONCAVE_POLYGON = [ + circle(300, 305, 305, true), + circle(40, 305, 105, false), + circle(40, 305, 205, false), + circle(40, 305, 305, false), + circle(40, 305, 405, false), + circle(40, 305, 505, false) +]; canvas.onmousedown = (event) => { START.x = event.offsetX; From c642375295820add563f721ff470deec7daf9c46 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Wed, 2 May 2018 17:39:27 +0200 Subject: [PATCH 07/17] update containLineInPath func --- src/sliceActions/helpers/comb.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index 80e7b41..cabe83a 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -205,16 +205,18 @@ function findClosestPath(map, start, end) { } function containLineInPath(path, start, end, vertices) { - const line = [start]; + let line = [start]; for (let i = 0; i < path.length; i ++) { const { edge: [indexA, indexB] } = path[i]; const vertexA = vertices[indexA]; const vertexB = vertices[indexB]; + const lastPoint = line[line.length - 1]; - const intersection = lineIntersection(start, end, vertexA, vertexB); + const intersection = lineIntersection(lastPoint, end, vertexA, vertexB); if (!intersection) { - const lastPoint = line[line.length - 1]; + line = containLineInPath(path.slice(0, i), start, lastPoint, vertices); + const distanceA = distanceTo(lastPoint, vertexA) + distanceTo(vertexA, end); const distanceB = distanceTo(lastPoint, vertexB) + distanceTo(vertexB, end); From 2c953496f7750c1953fe6934c6cb548bb93cff44 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Wed, 2 May 2018 17:42:31 +0200 Subject: [PATCH 08/17] share functions in comb.js --- comb.js | 214 +------------------------------ src/sliceActions/helpers/comb.js | 12 +- 2 files changed, 10 insertions(+), 216 deletions(-) diff --git a/comb.js b/comb.js index 27ec53a..f481e37 100644 --- a/comb.js +++ b/comb.js @@ -1,211 +1,5 @@ -import earcut from 'earcut'; import { add, divide, distanceTo, normalize, subtract, normal, dot } from './src/sliceActions/helpers/vector2.js'; - -function lineIntersection(a1, a2, b1, b2) { - // source: http://mathworld.wolfram.com/Line-LineIntersection.html - const intersection = { - x: ((a1.x * a2.y - a1.y * a2.x) * (b1.x - b2.x) - (a1.x - a2.x) * (b1.x * b2.y - b1.y * b2.x)) / ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)), - y: ((a1.x * a2.y - a1.y * a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x * b2.y - b1.y * b2.x)) / ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)) - }; - - const intersectionA = subtract(intersection, a1); - const directionA = subtract(a2, a1); - const normalA = normalize(directionA); - const distanceA = dot(normalA, intersectionA); - if (distanceA < 0 || distanceA > dot(normalA, directionA)) return false; - - const intersectionB = subtract(intersection, b1); - const directionB = subtract(b2, b1); - const normalB = normalize(directionB); - const distanceB = dot(normalB, intersectionB); - if (distanceB < 0 || distanceB > dot(normalB, directionB)) return false; - - return intersection; -} - -function pointIsInsideConvex(point, convex, vertices) { - for (let i = 0; i < convex.length; i ++) { - const vertexA = vertices[convex[i]]; - const vertexB = vertices[convex[(i + 1) % convex.length]]; - - const n = normalize(normal(subtract(vertexB, vertexA))); - const p = subtract(point, vertexA); - - if (dot(p, n) < 0) return false; - } - return true; -} - -function decompose(polygon) { - const vertices = polygon.reduce((points, path) => { - points.push(...path); - return points; - }, []); - const flatVertices = vertices.reduce((points, { x, y }) => { - points.push(x, y); - return points; - }, []); - let offset = 0; - const holes = polygon - .map(path => offset += path.length) - .slice(0, -1); - - const flatTrainglesIndexed = earcut(flatVertices, holes); - const convexPolygons = []; - for (let i = 0; i < flatTrainglesIndexed.length; i += 3) { - const face = [ - flatTrainglesIndexed[i], - flatTrainglesIndexed[i + 1], - flatTrainglesIndexed[i + 2] - ]; - const center = divide(face.reduce((total, point) => { - if (!total) { - return vertices[point]; - } else { - return add(total, vertices[point]); - } - }, null), face.length); - convexPolygons.push({ - center, - face, - connects: [] - }); - } - - for (let i = 0; i < convexPolygons.length; i ++) { - for (let j = i + 1; j < convexPolygons.length; j ++) { - const triangleIndexedA = convexPolygons[i]; - const triangleIndexedB = convexPolygons[j]; - - const overlap = []; - triangleIndexedA.face.map(index => { - if (triangleIndexedB.face.includes(index)) overlap.push(index); - }); - - if (overlap.length === 2) { - const distance = distanceTo(convexPolygons[i].center, convexPolygons[j].center); - triangleIndexedA.connects.push({ to: j, edge: overlap, distance }); - triangleIndexedB.connects.push({ to: i, edge: overlap, distance }); - } - } - } - - return { vertices, convexPolygons }; -} - -// const distanceMap = new WeakMap(); -// function findClosestPath(convexPolygons, start, end, visited = [], path = [], distance = 0) { -// if (start === end) return []; -// -// visited = [...visited, start]; -// -// const { connects } = convexPolygons[start]; -// -// const finish = connects.find(({ to }) => to === end); -// if (finish) return [...path, finish]; -// -// const posibilities = []; -// for (let i = 0; i < connects.length; i ++) { -// const connect = connects[i]; -// if (visited.includes(connect.to)) continue; -// -// const positibiltyDistance = distance + connect.distance; -// const posibility = findClosestPath(convexPolygons, connect.to, end, visited, [...path, connect], positibiltyDistance); -// if (posibility) { -// posibilities.push(posibility); -// distanceMap.set(posibility, positibiltyDistance); -// } -// } -// -// if (posibilities.length === 0) { -// return null; -// } else if (posibilities.length === 1) { -// return posibilities[0]; -// } else if (posibilities.length > 1) { -// return posibilities.sort((a, b) => distanceMap.get(a) - distanceMap.get(b))[0]; -// } -// } - -const findKey = _key => ({ key }) => _key === key; -function findClosestPath(map, start, end) { - // dijkstra's algorithm - const distances = { [start]: 0 }; - const open = [{ key: 0, nodes: [start] }]; - const predecessors = {}; - - while (open.length !== 0) { - const key = Math.min(...open.map(n => n.key).sort()); - const bucket = open.find(findKey(key)); - const node = bucket.nodes.shift(); - const currentDistance = key; - const { connects } = map[node]; - - if (bucket.nodes.length === 0) open.splice(open.indexOf(bucket), 1); - - for (let i = 0; i < connects.length; i ++) { - const { distance, to } = connects[i]; - const totalDistance = distance + currentDistance; - const vertexDistance = distances[to]; - - if ((typeof vertexDistance === 'undefined') || (vertexDistance > totalDistance)) { - distances[to] = totalDistance; - - let openNode = open.find(findKey(totalDistance)); - if (!openNode) { - openNode = { key: totalDistance, nodes: [] }; - open.push(openNode); - } - openNode.nodes.push(to); - - predecessors[to] = node; - } - } - } - - if (typeof distances[end] === 'undefined') return null; - - const nodes = []; - let node = end; - while (typeof node !== 'undefined') { - nodes.push(node); - node = predecessors[node]; - } - nodes.reverse(); - - const path = []; - for (let i = 1; i < nodes.length; i ++) { - const from = nodes[i - 1]; - const to = nodes[i]; - - const connection = map[from].connects.find(connect => connect.to === to); - path.push(connection); - } - - return path; -} - -function containLineInPath(path, start, end, vertices) { - const line = [start]; - - for (let i = 0; i < path.length; i ++) { - const { edge: [indexA, indexB] } = path[i]; - const vertexA = vertices[indexA]; - const vertexB = vertices[indexB]; - - const intersection = lineIntersection(start, end, vertexA, vertexB); - if (!intersection) { - const lastPoint = line[line.length - 1]; - const distanceA = distanceTo(lastPoint, vertexA) + distanceTo(vertexA, end); - const distanceB = distanceTo(lastPoint, vertexB) + distanceTo(vertexB, end); - - line.push(distanceA < distanceB ? vertexA : vertexB); - } - } - - line.push(end); - return line; -} - +import { pointIsInsideConvex, decompose, findClosestPath, containLineInPath } from './src/sliceActions/helpers/comb.js'; const canvas = document.createElement('canvas'); document.body.appendChild(canvas); @@ -228,8 +22,8 @@ function circle(radius = 10, x = 0, y = 0, clockWise = true, segments = 40) { return shape; } -const START = { x: 300, y: 40 }; -const END = { x: 300, y: 20 }; +const START = { x: 30, y: 550 }; +const END = { x: 400, y: 300 }; // const CONCAVE_POLYGON = [[ // { x: 10, y: 10 }, // { x: 600, y: 10 }, @@ -240,7 +34,7 @@ const END = { x: 300, y: 20 }; // { x: 160, y: 120 }, // { x: 120, y: 400 }, // { x: 400, y: 400 } -// ], circle(50, 300, 100, false)]; +// ]]; const CONCAVE_POLYGON = [ circle(300, 305, 305, true), circle(40, 305, 105, false), diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index cabe83a..e932808 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -43,7 +43,7 @@ function lineIntersection(a1, a2, b1, b2) { return intersection; } -function pointIsInsideConvex(point, convex, vertices) { +export function pointIsInsideConvex(point, convex, vertices) { for (let i = 0; i < convex.length; i ++) { const vertexA = vertices[convex[i]]; const vertexB = vertices[convex[(i + 1) % convex.length]]; @@ -56,7 +56,7 @@ function pointIsInsideConvex(point, convex, vertices) { return true; } -function decompose(polygon) { +export function decompose(polygon) { const vertices = polygon.reduce((points, path) => { points.push(...path); return points; @@ -114,7 +114,7 @@ function decompose(polygon) { } // const distanceMap = new WeakMap(); -// function findClosestPath(convexPolygons, start, end, visited = [], path = [], distance = 0) { +// export function findClosestPath(convexPolygons, start, end, visited = [], path = [], distance = 0) { // if (start === end) return []; // // visited = [...visited, start]; @@ -147,7 +147,7 @@ function decompose(polygon) { // } const findKey = _key => ({ key }) => _key === key; -function findClosestPath(map, start, end) { +export function findClosestPath(map, start, end) { // dijkstra's algorithm const distances = { [start]: 0 }; const open = [{ key: 0, nodes: [start] }]; @@ -204,7 +204,7 @@ function findClosestPath(map, start, end) { return path; } -function containLineInPath(path, start, end, vertices) { +export function containLineInPath(path, start, end, vertices) { let line = [start]; for (let i = 0; i < path.length; i ++) { @@ -215,7 +215,7 @@ function containLineInPath(path, start, end, vertices) { const intersection = lineIntersection(lastPoint, end, vertexA, vertexB); if (!intersection) { - line = containLineInPath(path.slice(0, i), start, lastPoint, vertices); + // line = containLineInPath(path.slice(0, i), start, lastPoint, vertices); const distanceA = distanceTo(lastPoint, vertexA) + distanceTo(vertexA, end); const distanceB = distanceTo(lastPoint, vertexB) + distanceTo(vertexB, end); From 5900bbcc50a925fa490d4294ce04f77b0c429412 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Sat, 5 May 2018 10:18:20 +0200 Subject: [PATCH 09/17] remove unused imports --- comb.js | 1 - 1 file changed, 1 deletion(-) diff --git a/comb.js b/comb.js index f481e37..78ec51b 100644 --- a/comb.js +++ b/comb.js @@ -1,4 +1,3 @@ -import { add, divide, distanceTo, normalize, subtract, normal, dot } from './src/sliceActions/helpers/vector2.js'; import { pointIsInsideConvex, decompose, findClosestPath, containLineInPath } from './src/sliceActions/helpers/comb.js'; const canvas = document.createElement('canvas'); From 1493ae3536ddfd0277ab001dbec347ef849ed3da Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Sat, 5 May 2018 10:19:30 +0200 Subject: [PATCH 10/17] add extra option within containLineInPath --- src/sliceActions/helpers/comb.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index e932808..aa70756 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -2,7 +2,6 @@ import { subtract, add, normalize, dot, distanceTo, divide, normal } from './vec import earcut from 'earcut'; const TRIANGULATED_OUTLINES = new WeakMap(); - export default function comb(outline, start, end) { if (distanceTo(start, end) < 3) return [start, end]; @@ -215,12 +214,12 @@ export function containLineInPath(path, start, end, vertices) { const intersection = lineIntersection(lastPoint, end, vertexA, vertexB); if (!intersection) { - // line = containLineInPath(path.slice(0, i), start, lastPoint, vertices); - const distanceA = distanceTo(lastPoint, vertexA) + distanceTo(vertexA, end); const distanceB = distanceTo(lastPoint, vertexB) + distanceTo(vertexB, end); + const newPoint = distanceA < distanceB ? vertexA : vertexB; - line.push(distanceA < distanceB ? vertexA : vertexB); + // line = containLineInPath(path.slice(0, i), start, newPoint, vertices); + line.push(newPoint); } } From caf36a5505dd510f0d73c24820c34eee04ec7bd4 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Thu, 24 May 2018 16:13:44 +0200 Subject: [PATCH 11/17] remove unused imports --- src/sliceActions/slicesToGCode.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sliceActions/slicesToGCode.js b/src/sliceActions/slicesToGCode.js index c1434e9..d5337e4 100644 --- a/src/sliceActions/slicesToGCode.js +++ b/src/sliceActions/slicesToGCode.js @@ -1,7 +1,6 @@ import GCode from './helpers/GCode.js'; import comb from './helpers/comb.js'; -import { divide } from './helpers/vector2.js'; -import { PRECISION, Z_OFFSET } from '../constants.js'; +import { Z_OFFSET } from '../constants.js'; const PROFILE_TYPES = ['support', 'innerShell', 'outerShell', 'innerInfill', 'outerInfill', 'brim']; From 86eed642559e69f728acc14f55d193a83dc7df51 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Thu, 24 May 2018 16:13:56 +0200 Subject: [PATCH 12/17] remove eruct --- package-lock.json | 39 +++++++++++++++++---------------------- package.json | 1 - 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a3194e..5ff7d3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ } }, "@doodle3d/doodle3d-core": { - "version": "github:doodle3d/doodle3d-core#0a3686d8df05e275805ebb8029528a45d7f31f39", + "version": "github:doodle3d/doodle3d-core#ac2256d2c541fb465f959131f6a216574be7f70b", "requires": { "@doodle3d/cal": "0.0.8", "@doodle3d/clipper-js": "1.0.10", @@ -41,7 +41,7 @@ "@doodle3d/potrace-js": "0.0.6", "@doodle3d/threejs-export-obj": "0.0.8", "@doodle3d/threejs-export-stl": "0.0.5", - "@doodle3d/touch-events": "0.0.7", + "@doodle3d/touch-events": "0.0.9", "babel-polyfill": "6.26.0", "bezier-js": "2.2.5", "blueimp-canvas-to-blob": "3.14.0", @@ -128,9 +128,9 @@ } }, "@doodle3d/touch-events": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@doodle3d/touch-events/-/touch-events-0.0.7.tgz", - "integrity": "sha512-EpL8IEGKKy2gqFFlxA4n84IeAyPTrEzle0jwmRv+mmVBzwGr6xDl5Ga5vJIrg2WcYs4Xc7qWbiKSHEXvEpDLlg==", + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@doodle3d/touch-events/-/touch-events-0.0.9.tgz", + "integrity": "sha512-VedelafzxzDlibHMlysYsayt5c+TpE/deOi4W6TEcm39PL6u+ihntDhgsflJUsg7bFM7bVrekgdn3OLreK8/UA==", "requires": { "eventdispatcher.js": "0.0.2", "pepjs": "0.4.3" @@ -3232,11 +3232,6 @@ } } }, - "earcut": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.3.tgz", - "integrity": "sha512-AxdCdWUk1zzK/NuZ7e1ljj6IGC+VAdC3Qb7QQDsXpfNrc5IM8tL9nNXUmEGE6jRHTfZ10zhzRhtDmWVsR5pd3A==" - }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -3803,9 +3798,9 @@ } }, "expand-template": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.0.tgz", - "integrity": "sha512-kkjwkMqj0h4w/sb32ERCDxCQkREMCAgS39DscDnSwDsbxnwwM1BTZySdC3Bn1lhY7vL08n9GoO/fVTynjDgRyQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", + "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==" }, "express": { "version": "4.16.2", @@ -8262,9 +8257,9 @@ } }, "node-abi": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.0.tgz", - "integrity": "sha512-hRUz0vG+eJfSqwU6rOgW6wNyX85ec8OEE9n4A+u+eoiE8oTePhCkUFTNmwQ+86Kyu429PCLNNyI2P2jL9qKXhw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.1.tgz", + "integrity": "sha512-pUlswqpHQ7zGPI9lGjZ4XDNIEUDbHxsltfIRb7dTnYdhgHWHOcB0MLZKLoCz6UMcGzSPG5wGl1HODZVQAUsH6w==", "requires": { "semver": "5.4.1" } @@ -9348,11 +9343,11 @@ "integrity": "sha512-/rI36cN2g7vDQnKWN8Uzupi++KjyqS9iS+/fpwG4Ea8d0Pip0PQ5bshUNzVwt+/D2MRfhVAplYMMvWLqWrCF/g==", "requires": { "detect-libc": "1.0.3", - "expand-template": "1.1.0", + "expand-template": "1.1.1", "github-from-package": "0.0.0", "minimist": "1.2.0", "mkdirp": "0.5.1", - "node-abi": "2.4.0", + "node-abi": "2.4.1", "noop-logger": "0.1.1", "npmlog": "4.1.2", "os-homedir": "1.0.2", @@ -10121,7 +10116,7 @@ "performance-now": "2.1.0", "qs": "6.5.1", "safe-buffer": "5.1.1", - "stringstream": "0.0.5", + "stringstream": "0.0.6", "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", "uuid": "3.2.1" @@ -10762,9 +10757,9 @@ } }, "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" }, "strip-ansi": { "version": "3.0.1", diff --git a/package.json b/package.json index fbf094a..e045faf 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "@doodle3d/doodle3d-api": "^1.0.5", "@doodle3d/doodle3d-core": "github:doodle3d/doodle3d-core", "babel-plugin-transform-class-properties": "^6.24.1", - "earcut": "^2.1.3", "file-saver": "^1.3.3", "lodash": "^4.17.4", "material-ui": "^0.19.4", From 7dceeda291c183abdbd4d05b286638462b53b14d Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Thu, 24 May 2018 16:14:03 +0200 Subject: [PATCH 13/17] update combing --- comb.js | 104 +++----- src/sliceActions/helpers/comb.js | 385 +++++++++++++--------------- src/sliceActions/helpers/vector2.js | 1 + 3 files changed, 208 insertions(+), 282 deletions(-) diff --git a/comb.js b/comb.js index 78ec51b..180a67c 100644 --- a/comb.js +++ b/comb.js @@ -1,9 +1,9 @@ -import { pointIsInsideConvex, decompose, findClosestPath, containLineInPath } from './src/sliceActions/helpers/comb.js'; +import comb from './src/sliceActions/helpers/comb.js'; const canvas = document.createElement('canvas'); document.body.appendChild(canvas); -canvas.width = 610; -canvas.height = 610; +canvas.width = 800; +canvas.height = 800; const context = canvas.getContext('2d'); context.lineJoin = 'bevel'; @@ -21,27 +21,28 @@ function circle(radius = 10, x = 0, y = 0, clockWise = true, segments = 40) { return shape; } -const START = { x: 30, y: 550 }; +const START = { x: 200, y: 400 }; const END = { x: 400, y: 300 }; -// const CONCAVE_POLYGON = [[ -// { x: 10, y: 10 }, -// { x: 600, y: 10 }, -// { x: 500, y: 200 }, -// { x: 600, y: 600 }, -// { x: 10, y: 600 } -// ], [ -// { x: 160, y: 120 }, -// { x: 120, y: 400 }, -// { x: 400, y: 400 } -// ]]; -const CONCAVE_POLYGON = [ - circle(300, 305, 305, true), - circle(40, 305, 105, false), - circle(40, 305, 205, false), - circle(40, 305, 305, false), - circle(40, 305, 405, false), - circle(40, 305, 505, false) -]; + +const POLYGON = [[ + { x: 10, y: 10 }, + { x: 600, y: 10 }, + { x: 500, y: 200 }, + { x: 600, y: 600 }, + { x: 10, y: 600 } +], [ + { x: 160, y: 120 }, + { x: 120, y: 400 }, + { x: 400, y: 400 } +]]; +// const POLYGON = [ +// circle(300, 305, 305, true, 4), +// circle(40, 305, 105, false, 4), +// circle(40, 305, 205, false, 4), +// circle(40, 305, 305, false, 4), +// circle(40, 305, 405, false, 4), +// circle(40, 305, 505, false, 4) +// ]; canvas.onmousedown = (event) => { START.x = event.offsetX; @@ -56,20 +57,13 @@ canvas.onmousemove = (event) => { compute(); function compute() { - const { convexPolygons, vertices } = decompose(CONCAVE_POLYGON); - const startPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(START, face, vertices)); - const endPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(END, face, vertices)); - if (startPolygon === -1 || endPolygon === -1) return; - - const path = findClosestPath(convexPolygons, startPolygon, endPolygon); - if (!path) return; - const line = containLineInPath(path, START, END, vertices); + const path = comb(POLYGON, START, END); // draw context.clearRect(0, 0, canvas.width, canvas.height); context.beginPath(); - for (const shape of CONCAVE_POLYGON) { + for (const shape of POLYGON) { let first = true; for (const { x, y } of shape) { if (first) { @@ -84,48 +78,12 @@ function compute() { context.fillStyle = 'lightgray'; context.fill(); - context.fillStyle = 'black'; - context.strokeStyle = 'black'; - context.textAlign = 'center'; - context.textBaseline = 'middle'; - context.lineWidth = 1; - context.font = '14px arial'; - for (let i = 0; i < convexPolygons.length; i ++) { - const { face, center } = convexPolygons[i]; - - context.beginPath(); - for (const index of face) { - const vertex = vertices[index]; - context.lineTo(vertex.x, vertex.y); - } - context.closePath(); - context.stroke(); - - context.fillText(i, center.x, center.y); - } - - if (path) { - context.beginPath(); - for (const { edge: [indexA, indexB] } of path) { - const pointA = vertices[indexA]; - const pointB = vertices[indexB]; - context.moveTo(pointA.x, pointA.y); - context.lineTo(pointB.x, pointB.y); - } - context.strokeStyle = 'blue'; - context.lineWidth = 3; - context.stroke(); - } - - if (line) { - context.beginPath(); - for (const point of line) { - context.lineTo(point.x, point.y); - } - context.strokeStyle = 'green'; - context.lineWidth = 2; - context.stroke(); + context.beginPath(); + for (const { x, y } of path) { + context.lineTo(x, y); } + context.lineWidth = 2; + context.stroke(); context.beginPath(); context.arc(START.x, START.y, 3, 0, Math.PI * 2); diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index aa70756..a7fd1e1 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -1,228 +1,195 @@ -import { subtract, add, normalize, dot, distanceTo, divide, normal } from './vector2.js'; -import earcut from 'earcut'; +import { angle, subtract, distanceTo, normal } from './vector2.js'; -const TRIANGULATED_OUTLINES = new WeakMap(); -export default function comb(outline, start, end) { - if (distanceTo(start, end) < 3) return [start, end]; +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); - if (!TRIANGULATED_OUTLINES.has(outline)) TRIANGULATED_OUTLINES.set(outline, decompose(outline)); - const { convexPolygons, vertices } = TRIANGULATED_OUTLINES.get(outline); + points = [...points, start, end]; + graph = [...graph]; - const startPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(start, face, vertices)); - const endPolygon = convexPolygons.findIndex(({ face }) => pointIsInsideConvex(end, face, vertices)); - if (startPolygon === -1 || endPolygon === -1) return [start, end]; - if (startPolygon === endPolygon) return [start, end]; + const startNode = createNode(graph, points, edges, normals, start); + const endNode = createNode(graph, points, edges, normals, end); - const path = findClosestPath(convexPolygons, startPolygon, endPolygon); - if (!path) return [start, end]; - - const line = containLineInPath(path, start, end, vertices); - return line; -} - -function lineIntersection(a1, a2, b1, b2) { - // source: http://mathworld.wolfram.com/Line-LineIntersection.html - const intersection = { - x: ((a1.x * a2.y - a1.y * a2.x) * (b1.x - b2.x) - (a1.x - a2.x) * (b1.x * b2.y - b1.y * b2.x)) / ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)), - y: ((a1.x * a2.y - a1.y * a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x * b2.y - b1.y * b2.x)) / ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)) - }; - - const intersectionA = subtract(intersection, a1); - const directionA = subtract(a2, a1); - const normalA = normalize(directionA); - const distanceA = dot(normalA, intersectionA); - if (distanceA < 0 || distanceA > dot(normalA, directionA)) return false; - - const intersectionB = subtract(intersection, b1); - const directionB = subtract(b2, b1); - const normalB = normalize(directionB); - const distanceB = dot(normalB, intersectionB); - if (distanceB < 0 || distanceB > dot(normalB, directionB)) return false; - - return intersection; -} - -export function pointIsInsideConvex(point, convex, vertices) { - for (let i = 0; i < convex.length; i ++) { - const vertexA = vertices[convex[i]]; - const vertexB = vertices[convex[(i + 1) % convex.length]]; - - const n = normalize(normal(subtract(vertexB, vertexA))); - const p = subtract(point, vertexA); - - if (dot(p, n) < 0) return false; + 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; } -export function decompose(polygon) { - const vertices = polygon.reduce((points, path) => { - points.push(...path); - return points; - }, []); - const flatVertices = vertices.reduce((points, { x, y }) => { - points.push(x, y); - return points; - }, []); - let offset = 0; - const holes = polygon - .map(path => offset += path.length) - .slice(0, -1); - - const flatTrainglesIndexed = earcut(flatVertices, holes); - const convexPolygons = []; - for (let i = 0; i < flatTrainglesIndexed.length; i += 3) { - const face = [ - flatTrainglesIndexed[i], - flatTrainglesIndexed[i + 1], - flatTrainglesIndexed[i + 2] - ]; - const center = divide(face.reduce((total, point) => { - if (!total) { - return vertices[point]; - } else { - return add(total, vertices[point]); - } - }, null), face.length); - convexPolygons.push({ - center, - face, - connects: [] - }); +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; +} - for (let i = 0; i < convexPolygons.length; i ++) { - for (let j = i + 1; j < convexPolygons.length; j ++) { - const triangleIndexedA = convexPolygons[i]; - const triangleIndexedB = convexPolygons[j]; +function lineSegmentsCross(a, b, c, d) { + const denominator = ((b.x - a.x) * (d.y - c.y)) - ((b.y - a.y) * (d.x - c.x)); - const overlap = []; - triangleIndexedA.face.map(index => { - if (triangleIndexedB.face.includes(index)) overlap.push(index); - }); + if (denominator === 0.0) return false; - if (overlap.length === 2) { - const distance = distanceTo(convexPolygons[i].center, convexPolygons[j].center); - triangleIndexedA.connects.push({ to: j, edge: overlap, distance }); - triangleIndexedB.connects.push({ to: i, edge: overlap, distance }); + 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; } } } - return { vertices, convexPolygons }; + 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(); } -// const distanceMap = new WeakMap(); -// export function findClosestPath(convexPolygons, start, end, visited = [], path = [], distance = 0) { -// if (start === end) return []; -// -// visited = [...visited, start]; -// -// const { connects } = convexPolygons[start]; -// -// const finish = connects.find(({ to }) => to === end); -// if (finish) return [...path, finish]; -// -// const posibilities = []; -// for (let i = 0; i < connects.length; i ++) { -// const connect = connects[i]; -// if (visited.includes(connect.to)) continue; -// -// const positibiltyDistance = distance + connect.distance; -// const posibility = findClosestPath(convexPolygons, connect.to, end, visited, [...path, connect], positibiltyDistance); -// if (posibility) { -// posibilities.push(posibility); -// distanceMap.set(posibility, positibiltyDistance); -// } -// } -// -// if (posibilities.length === 0) { -// return null; -// } else if (posibilities.length === 1) { -// return posibilities[0]; -// } else if (posibilities.length > 1) { -// return posibilities.sort((a, b) => distanceMap.get(a) - distanceMap.get(b))[0]; -// } -// } - -const findKey = _key => ({ key }) => _key === key; -export function findClosestPath(map, start, end) { - // dijkstra's algorithm - const distances = { [start]: 0 }; - const open = [{ key: 0, nodes: [start] }]; - const predecessors = {}; - - while (open.length !== 0) { - const key = Math.min(...open.map(n => n.key).sort()); - const bucket = open.find(findKey(key)); - const node = bucket.nodes.shift(); - const currentDistance = key; - const { connects } = map[node]; - - if (bucket.nodes.length === 0) open.splice(open.indexOf(bucket), 1); - - for (let i = 0; i < connects.length; i ++) { - const { distance, to } = connects[i]; - const totalDistance = distance + currentDistance; - const vertexDistance = distances[to]; - - if ((typeof vertexDistance === 'undefined') || (vertexDistance > totalDistance)) { - distances[to] = totalDistance; - - let openNode = open.find(findKey(totalDistance)); - if (!openNode) { - openNode = { key: totalDistance, nodes: [] }; - open.push(openNode); - } - openNode.nodes.push(to); - - predecessors[to] = node; - } - } - } - - if (typeof distances[end] === 'undefined') return null; - - const nodes = []; - let node = end; - while (typeof node !== 'undefined') { - nodes.push(node); - node = predecessors[node]; - } - nodes.reverse(); - - const path = []; - for (let i = 1; i < nodes.length; i ++) { - const from = nodes[i - 1]; - const to = nodes[i]; - - const connection = map[from].connects.find(connect => connect.to === to); - path.push(connection); - } - - return path; -} - -export function containLineInPath(path, start, end, vertices) { - let line = [start]; - - for (let i = 0; i < path.length; i ++) { - const { edge: [indexA, indexB] } = path[i]; - const vertexA = vertices[indexA]; - const vertexB = vertices[indexB]; - const lastPoint = line[line.length - 1]; - - const intersection = lineIntersection(lastPoint, end, vertexA, vertexB); - if (!intersection) { - const distanceA = distanceTo(lastPoint, vertexA) + distanceTo(vertexA, end); - const distanceB = distanceTo(lastPoint, vertexB) + distanceTo(vertexB, end); - const newPoint = distanceA < distanceB ? vertexA : vertexB; - - // line = containLineInPath(path.slice(0, i), start, newPoint, vertices); - line.push(newPoint); - } - } - - line.push(end); - return line; +function pointOnLine([a, b], point) { + return (a.x - point.x) * (a.y - point.y) === (b.x - point.x) * (b.y - point.y); } diff --git a/src/sliceActions/helpers/vector2.js b/src/sliceActions/helpers/vector2.js index bf10575..ad5d740 100644 --- a/src/sliceActions/helpers/vector2.js +++ b/src/sliceActions/helpers/vector2.js @@ -23,6 +23,7 @@ export const almostEquals = (a, b) => Math.abs(a.x - b.x) < 0.001 && Math.abs(a. export const dot = (a, b) => a.x * b.x + a.y * b.y; export const length = (v) => Math.sqrt(v.x * v.x + v.y * v.y); export const distanceTo = (a, b) => length(subtract(a, b)); +export const angle = (v) => Math.atan2(v.y, v.x); export const normalize = (v) => { const l = length(v); From 204492980804027e7ce9dd6783f1d0bebcb1d11c Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Thu, 24 May 2018 16:27:22 +0200 Subject: [PATCH 14/17] turn of combing by default --- src/settings/default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/default.yml b/src/settings/default.yml index 8167357..a63d75f 100644 --- a/src/settings/default.yml +++ b/src/settings/default.yml @@ -35,7 +35,7 @@ filamentThickness: 2.85 temperature: 210 bedTemperature: 50 layerHeight: 0.15 -combing: true +combing: false thickness: top: 0.45 bottom: 0.45 From fd6e5cebbd4d41f4e45ef724268e59dbe59c8b36 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Mon, 28 May 2018 13:53:09 +0200 Subject: [PATCH 15/17] remove normals --- src/sliceActions/helpers/comb.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index a7fd1e1..d39d190 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -1,15 +1,15 @@ -import { angle, subtract, distanceTo, normal } from './vector2.js'; +import { angle, subtract, distanceTo } 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); + let { edges, graph, points } = 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); + const startNode = createNode(graph, points, edges, start); + const endNode = createNode(graph, points, edges, end); let result; if (graph[startNode].some(node => node.to === endNode)) { @@ -31,7 +31,6 @@ function createGraph(polygons) { 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 ++) { @@ -43,8 +42,6 @@ function createGraph(polygons) { edges.push([point, nextPoint]); nextPoints.set(point, nextPoint); previousPoints.set(point, previousPoint); - - normals.set(point, normal(subtract(nextPoint, point))); } } @@ -68,10 +65,10 @@ function createGraph(polygons) { } } } - return { graph, edges, points, normals }; + return { graph, edges, points }; } -function createNode(graph, points, edges, normals, point) { +function createNode(graph, points, edges, point) { const node = []; const to = graph.length; graph.push(node); From 3944202a833a4021e402324b209cf735d35c9072 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Mon, 28 May 2018 13:53:32 +0200 Subject: [PATCH 16/17] add tau constant --- src/sliceActions/helpers/comb.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index d39d190..2f6ed67 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -131,9 +131,10 @@ function lineSegmentsCross(a, b, c, d) { return (r > 0.0 && r < 1.0) && (s >= 0.0 && s <= 1.0); } +const TAU = Math.PI * 2.0; function normalizeAngle(a) { - a %= Math.PI * 2; - return a > 0.0 ? a : a + Math.PI * 2; + a %= TAU; + return a > 0.0 ? a : a + TAU; } function betweenAngles(n, a, b) { From fc3dc7355c9bdb6d51e08efd69605fd5a4a21ce8 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Mon, 28 May 2018 13:53:53 +0200 Subject: [PATCH 17/17] reformat --- src/sliceActions/helpers/comb.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/sliceActions/helpers/comb.js b/src/sliceActions/helpers/comb.js index 2f6ed67..e1165d4 100644 --- a/src/sliceActions/helpers/comb.js +++ b/src/sliceActions/helpers/comb.js @@ -54,15 +54,15 @@ function createGraph(polygons) { const nextPoint = nextPoints.get(a); const previousPoint = previousPoints.get(a); - if (lineIsVisible(previousPoint, nextPoint, edges, a, b)) { - const distance = distanceTo(a, b); + if (!lineIsVisible(previousPoint, nextPoint, edges, a, b)) continue; - const connectNodeA = graph[i]; - connectNodeA.push({ to: j, distance }); + const distance = distanceTo(a, b); - const connectNodeB = graph[j]; - connectNodeB.push({ to: i, distance }); - } + const connectNodeA = graph[i]; + connectNodeA.push({ to: j, distance }); + + const connectNodeB = graph[j]; + connectNodeB.push({ to: i, distance }); } } return { graph, edges, points }; @@ -86,8 +86,8 @@ function createNode(graph, points, edges, point) { if (!lineIsVisible(previousPoint, nextPoint, edges, point, b)) continue; const distance = distanceTo(point, b); - node.push({ to: i, distance }); + node.push({ to: i, distance }); graph[i] = [...graph[i], { to, distance }]; } @@ -101,6 +101,7 @@ function lineIsVisible(previousPoint, nextPoint, edges, a, b) { 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; } @@ -149,7 +150,10 @@ function shortestPath(graph, start, end) { const distances = graph.map(() => Infinity); distances[start] = 0; const traverse = []; - const queue = graph.map((node, i) => i); + const queue = []; + for (let i = 0; i < distances.length; i ++) { + queue.push(i); + } while (queue.length > 0) { let queueIndex;