UNPKG

@jscad/modeling

Version:

Constructive Solid Geometry (CSG) Library for JSCAD

120 lines (110 loc) 3.56 kB
const vec2 = require('../../maths/vec2') const toSides = require('./toSides') /* * Create a list of edges which SHARE vertices. * This allows the edges to be traversed in order. */ const toSharedVertices = (sides) => { const unique = new Map() // {key: vertex} const getUniqueVertex = (vertex) => { const key = vertex.toString() if (unique.has(key)) { return unique.get(key) } else { unique.set(key, vertex) return vertex } } return sides.map((side) => side.map(getUniqueVertex)) } /* * Convert a list of sides into a map from vertex to edges. */ const toVertexMap = (sides) => { const vertexMap = new Map() // first map to edges with shared vertices const edges = toSharedVertices(sides) // construct adjacent edges map edges.forEach((edge) => { if (vertexMap.has(edge[0])) { vertexMap.get(edge[0]).push(edge) } else { vertexMap.set(edge[0], [edge]) } }) return vertexMap } /** * Create the outline(s) of the given geometry. * @param {geom2} geometry - geometry to create outlines from * @returns {Array} an array of outlines, where each outline is an array of ordered points * @alias module:modeling/geometries/geom2.toOutlines * * @example * let geometry = subtract(rectangle({size: [5, 5]}), rectangle({size: [3, 3]})) * let outlines = toOutlines(geometry) // returns two outlines */ const toOutlines = (geometry) => { const vertexMap = toVertexMap(toSides(geometry)) // {vertex: [edges]} const outlines = [] while (true) { let startSide for (const [vertex, edges] of vertexMap) { startSide = edges.shift() if (!startSide) { vertexMap.delete(vertex) continue } break } if (startSide === undefined) break // all starting sides have been visited const connectedVertexPoints = [] const startVertex = startSide[0] while (true) { connectedVertexPoints.push(startSide[0]) const nextVertex = startSide[1] if (nextVertex === startVertex) break // the outline has been closed const nextPossibleSides = vertexMap.get(nextVertex) if (!nextPossibleSides) { throw new Error(`geometry is not closed at vertex ${nextVertex}`) } const nextSide = popNextSide(startSide, nextPossibleSides) if (nextPossibleSides.length === 0) { vertexMap.delete(nextVertex) } startSide = nextSide } // inner loop // due to the logic of fromPoints() // move the first point to the last if (connectedVertexPoints.length > 0) { connectedVertexPoints.push(connectedVertexPoints.shift()) } outlines.push(connectedVertexPoints) } // outer loop vertexMap.clear() return outlines } // find the first counter-clockwise edge from startSide and pop from nextSides const popNextSide = (startSide, nextSides) => { if (nextSides.length === 1) { return nextSides.pop() } const v0 = vec2.create() const startAngle = vec2.angleDegrees(vec2.subtract(v0, startSide[1], startSide[0])) let bestAngle let bestIndex nextSides.forEach((nextSide, index) => { const nextAngle = vec2.angleDegrees(vec2.subtract(v0, nextSide[1], nextSide[0])) let angle = nextAngle - startAngle if (angle < -180) angle += 360 if (angle >= 180) angle -= 360 if (bestIndex === undefined || angle > bestAngle) { bestIndex = index bestAngle = angle } }) const nextSide = nextSides[bestIndex] nextSides.splice(bestIndex, 1) // remove side from list return nextSide } module.exports = toOutlines