diff --git a/Package.resolved b/Package.resolved index 72ae3766..4b059c11 100644 --- a/Package.resolved +++ b/Package.resolved @@ -10,6 +10,15 @@ "version": "0.8.1" } }, + { + "package": "MiniGraphviz", + "repositoryURL": "https://github.com/LuizZak/MiniGraphviz.git", + "state": { + "branch": null, + "revision": "aa15c50cf8027dae6dde860a40987c238070d849", + "version": "0.1.0" + } + }, { "package": "MiniP5Printer", "repositoryURL": "https://github.com/LuizZak/MiniP5Printer", diff --git a/Package.swift b/Package.swift index 57e2e1f7..f870bc71 100644 --- a/Package.swift +++ b/Package.swift @@ -19,6 +19,7 @@ let testCommons: Target = .target( name: "TestCommons", dependencies: [ .product(name: "MiniP5Printer", package: "MiniP5Printer"), + .product(name: "MiniGraphviz", package: "MiniGraphviz"), "Geometria", "GeometriaAlgorithms", "GeometriaClipping", @@ -95,6 +96,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections.git", from: "1.1.2"), .package(url: "https://github.com/LuizZak/MiniP5Printer.git", .exactItem("0.0.2")), .package(url: "https://github.com/LuizZak/MiniDigraph.git", .exactItem("0.8.1")), + .package(url: "https://github.com/LuizZak/MiniGraphviz.git", .exactItem("0.1.0")), ], targets: [ geometriaTarget.applyReportBuildTime(), diff --git a/Sources/Geometria/Angles/Angle.swift b/Sources/Geometria/Angles/Angle.swift index cbe22e1e..61e647fe 100644 --- a/Sources/Geometria/Angles/Angle.swift +++ b/Sources/Geometria/Angles/Angle.swift @@ -9,6 +9,12 @@ public struct Angle: Hashable, Cust /// Gets the radian value associated with this angle. public let radians: Scalar + /// Gets the degrees value associated with this angle. + @inlinable + public var degrees: Scalar { + radians * (180 / .pi) + } + public var description: String { "\(type(of: self))(radians: \(radians))" } diff --git a/Sources/GeometriaClipping/2D/Graph/Simplex2Graph+Creation.swift b/Sources/GeometriaClipping/2D/Graph/Simplex2Graph+Creation.swift index 5e146396..2e61177e 100644 --- a/Sources/GeometriaClipping/2D/Graph/Simplex2Graph+Creation.swift +++ b/Sources/GeometriaClipping/2D/Graph/Simplex2Graph+Creation.swift @@ -333,6 +333,8 @@ extension Simplex2Graph { var hasMergedNodes = false var hasMergedEdges = false + var nodesToMerge: OrderedSet> = [] + // MARK: Edge-vertex interferences for node in nodes { let nodeAABB = AABB2(center: node.location, size: .init(repeating: tolerance * 2)) @@ -349,8 +351,10 @@ extension Simplex2Graph { return } - if distanceSquared.squareRoot() < tolerance { - splitEdge(edge, ratio: ratio) + if distanceSquared < tolerance { + let midNode = splitEdge(edge, ratio: ratio) + + nodesToMerge.append([node, midNode]) } } } @@ -365,8 +369,6 @@ extension Simplex2Graph { areClose(n1.location, n2.location) } - var nodesToMerge: OrderedSet> = [] - for node in nodes { let neighbors = nodeTree.nearestNeighbors( to: node.location, @@ -582,13 +584,17 @@ extension Simplex2Graph { /// /// If `period` matches the edge's `startPeriod` or `endPeriod`, then the edge /// is not split and nothing is done. + @discardableResult @inlinable mutating func splitEdge( _ edge: Edge, ratio: Scalar - ) { - guard ratio > 0 && ratio < 1 else { - return + ) -> Node { + guard ratio > 0 else { + return edge.start + } + guard ratio < 1 else { + return edge.end } let kind: Node.Kind @@ -617,6 +623,8 @@ extension Simplex2Graph { addNode(midNode) splitEdge(edge, ratio: ratio, midNode: midNode) + + return midNode } /// Splits an edge into two sub-edges, covering the same period range, but with @@ -844,9 +852,8 @@ extension Parametric2Contour { for selfSimplex in self.allSimplexes() { for otherSimplex in other.allSimplexes() { - atoms.append( - contentsOf: selfSimplex.intersectionPeriods(with: otherSimplex) - ) + let periods = selfSimplex.intersectionPeriods(with: otherSimplex) + atoms.append(contentsOf: periods) } } diff --git a/Sources/TestCommons/MiniGraphviz+Clipping.swift b/Sources/TestCommons/MiniGraphviz+Clipping.swift new file mode 100644 index 00000000..2bfeb003 --- /dev/null +++ b/Sources/TestCommons/MiniGraphviz+Clipping.swift @@ -0,0 +1,29 @@ +import Geometria +import GeometriaClipping +import MiniGraphviz + +extension GraphViz { + static func printGraph(_ graph: Simplex2Graph) where Vector.Scalar: CustomStringConvertible { + let nodes = graph.nodes + let edges = graph.edges + + let graph = GraphViz() + + for node in nodes { + graph.createNode(label: "\(node.id)") + } + + for edge in edges { + let label: String + switch edge.kind { + case .line: + label = "line" + case .circleArc(_, _, _, let sweepAngle): + label = "arc (\(sweepAngle.degrees)°)" + } + graph.addConnection(fromLabel: "\(edge.start.id)", toLabel: "\(edge.end.id)", label: label) + } + + print(graph.generateFile()) + } +} diff --git a/Tests/GeometriaClippingTests/2D/Boolean/Union2ParametricTests.swift b/Tests/GeometriaClippingTests/2D/Boolean/Union2ParametricTests.swift index fd2a86e2..7c4c5273 100644 --- a/Tests/GeometriaClippingTests/2D/Boolean/Union2ParametricTests.swift +++ b/Tests/GeometriaClippingTests/2D/Boolean/Union2ParametricTests.swift @@ -499,6 +499,22 @@ class Union2ParametricTests: XCTestCase { } } + func testUnion_capsuleSequence_long_3() { + let inputs: [Capsule2Parametric] = [Capsule2Parametric>(start: Vector2(x: 110.9805095512569, y: 498.69591675104044), startRadius: 48.0317197264079, end: Vector2(x: 416.52708500302657, y: 233.8149454674293), endRadius: 36.588642093089966, startPeriod: 0.0, endPeriod: 1.0), Capsule2Parametric>(start: Vector2(x: 416.52708500302657, y: 233.8149454674293), startRadius: 36.588642093089966, end: Vector2(x: 319.21714610719965, y: 338.621547076579), endRadius: 47.257943707318276, startPeriod: 0.0, endPeriod: 1.0), Capsule2Parametric>(start: Vector2(x: 319.21714610719965, y: 338.621547076579), startRadius: 47.257943707318276, end: Vector2(x: 598.0389757446262, y: 414.0995939891976), endRadius: 48.88305781959036, startPeriod: 0.0, endPeriod: 1.0), Capsule2Parametric>(start: Vector2(x: 598.0389757446262, y: 414.0995939891976), startRadius: 48.88305781959036, end: Vector2(x: 918.9250986336896, y: 115.50051959924077), endRadius: 33.94632559108497, startPeriod: 0.0, endPeriod: 1.0), Capsule2Parametric>(start: Vector2(x: 918.9250986336896, y: 115.50051959924077), startRadius: 33.94632559108497, end: Vector2(x: 358.181382248346, y: 335.7497615451311), endRadius: 38.74178996766864, startPeriod: 0.0, endPeriod: 1.0), Capsule2Parametric>(start: Vector2(x: 358.181382248346, y: 335.7497615451311), startRadius: 38.74178996766864, end: Vector2(x: 189.35176796497183, y: 331.4399380733415), endRadius: 29.50247300985871, startPeriod: 0.0, endPeriod: 1.0)] + + let sut = Union2Parametric(contours: inputs.flatMap({ $0.allContours() }), tolerance: 1e-5) + + TestFixture.beginFixture(lineScale: 1.0, renderScale: 0.45) { fixture in + fixture.add(inputs, category: "inputs") + + fixture.assertions(on: sut) + .assertAllSimplexes( + accuracy: accuracy, + [[Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 110.9805095512569, y: 498.69591675104044), radius: 48.0317197264079, startAngle: Angle(radians: 0.8282656212596273), sweepAngle: Angle(radians: 1.5707963267948966), startPeriod: 0.0, endPeriod: 0.031958306431205064)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 110.9805095512569, y: 498.69591675104044), radius: 48.0317197264079, startAngle: Angle(radians: 2.399061948054524), sweepAngle: Angle(radians: 1.5707963267948966), startPeriod: 0.031958306431205064, endPeriod: 0.06391661286241013)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 110.9805095512569, y: 498.69591675104044), radius: 48.0317197264079, startAngle: Angle(radians: 3.9698582748494204), sweepAngle: Angle(radians: 0.05660366239195147), startPeriod: 0.06391661286241013, endPeriod: 0.0650682307755622)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 80.55766692072845, y: 461.5274254424831), end: Vector2(x: 202.07840514413593, y: 362.0612871850714), startPeriod: 0.0650682307755622, endPeriod: 0.1315862557108902)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 202.07840514413593, y: 362.0612871850714), end: Vector2(x: 186.98652020697256, y: 360.84744590281804), startPeriod: 0.1315862557108902, endPeriod: 0.13799952430606413)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 189.35176796497183, y: 331.4399380733415), radius: 29.50247300985871, startAngle: Angle(radians: 1.651053627682679), sweepAngle: Angle(radians: 1.5707963267948966), startPeriod: 0.13799952430606413, endPeriod: 0.15762924146584847)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 189.35176796497183, y: 331.4399380733415), radius: 29.50247300985871, startAngle: Angle(radians: 3.221849954477576), sweepAngle: Angle(radians: 1.4613259411032358), startPeriod: 0.15762924146584847, endPeriod: 0.1758909437711008)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 188.4900322993723, y: 301.95005293724654), end: Vector2(x: 278.7400722555543, y: 299.3128206152788), startPeriod: 0.1758909437711008, endPeriod: 0.21413539968955667)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 278.7400722555543, y: 299.3128206152788), end: Vector2(x: 393.3521808604459, y: 205.50147608979154), startPeriod: 0.21413539968955667, endPeriod: 0.27687177875972757)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 416.52708500302657, y: 233.8149454674293), radius: 36.588642093089966, startAngle: Angle(radians: -2.256723369938214), sweepAngle: Angle(radians: 1.5089228046683982), startPeriod: 0.27687177875972757, endPeriod: 0.30025740785407506)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 416.52708500302657, y: 233.8149454674293), radius: 36.588642093089966, startAngle: Angle(radians: -0.7478005652698156), sweepAngle: Angle(radians: 1.421453534874056), startPeriod: 0.30025740785407506, endPeriod: 0.32228741829501273)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 445.1228649901167, y: 256.640591069387), end: Vector2(x: 440.219457475034, y: 262.78353944129736), startPeriod: 0.32228741829501273, endPeriod: 0.3256167491331845)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 440.219457475034, y: 262.78353944129736), end: Vector2(x: 906.7665394903199, y: 83.80631727936634), startPeriod: 0.3256167491331845, endPeriod: 0.537279368940178)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 918.9250986336896, y: 115.50051959924077), radius: 33.94632559108497, startAngle: Angle(radians: -1.9371035626186832), sweepAngle: Angle(radians: 1.2217500110984842), startPeriod: 0.537279368940178, endPeriod: 0.5548469055500806)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 918.9250986336896, y: 115.50051959924077), radius: 33.94632559108497, startAngle: Angle(radians: -0.7153535515201987), sweepAngle: Angle(radians: 1.5026295331423438), startPeriod: 0.5548469055500806, endPeriod: 0.5764532072442095)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 942.8836588189453, y: 139.54922881585847), end: Vector2(x: 632.5395487055349, y: 448.7299824820002), startPeriod: 0.5764532072442095, endPeriod: 0.762011450387202)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 598.0389757446262, y: 414.0995939891976), radius: 48.88305781959036, startAngle: Angle(radians: 0.7872759816221461), sweepAngle: Angle(radians: 1.0422617292624414), startPeriod: 0.762011450387202, endPeriod: 0.7835924173205498)), Parametric2GeometrySimplex>.circleArc2(CircleArc2Simplex>(center: Vector2(x: 598.0389757446262, y: 414.0995939891976), radius: 48.88305781959036, startAngle: Angle(radians: 1.8295377108845876), sweepAngle: Angle(radians: 0.01125207916006099), startPeriod: 0.7835924173205498, endPeriod: 0.7838254017441998)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 585.0006354627674, y: 461.21174767047046), end: Vector2(x: 306.7651781612669, y: 384.20950313645886), startPeriod: 0.7838254017441998, endPeriod: 0.9061105557779229)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 306.7651781612669, y: 384.20950313645886), end: Vector2(x: 143.4573776400863, y: 534.0837556087183), startPeriod: 0.9061105557779229, endPeriod: 1.0))], [Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 474.36380532422453, y: 330.79968554139725), end: Vector2(x: 585.5196060376405, y: 360.21977007895777), startPeriod: 0.0, endPeriod: 0.18662569292398146)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 585.5196060376405, y: 360.21977007895777), end: Vector2(x: 744.2413691085113, y: 222.30952735308094), startPeriod: 0.18662569292398146, endPeriod: 0.5279019083759146)), Parametric2GeometrySimplex>.lineSegment2(LineSegment2Simplex>(start: Vector2(x: 744.2413691085113, y: 222.30952735308094), end: Vector2(x: 474.36380532422453, y: 330.79968554139725), startPeriod: 0.5279019083759146, endPeriod: 1.0))]] + ) + } + } + #if GEOMETRIA_PERFORMANCE_TESTS func testPerformance_overlapping_circles() {