elkrb 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +364 -0
  3. data/LICENSE +25 -0
  4. data/README.adoc +695 -557
  5. data/exe/elkrb +36 -2
  6. data/lib/elkrb/atomic_destination.rb +53 -0
  7. data/lib/elkrb/best_effort_write.rb +49 -0
  8. data/lib/elkrb/cli/layout_flag_options.rb +54 -0
  9. data/lib/elkrb/cli.rb +155 -82
  10. data/lib/elkrb/command_resolver.rb +186 -0
  11. data/lib/elkrb/commands/batch_command.rb +28 -5
  12. data/lib/elkrb/commands/convert_command.rb +22 -52
  13. data/lib/elkrb/commands/diagram_command.rb +234 -102
  14. data/lib/elkrb/commands/options_command.rb +87 -0
  15. data/lib/elkrb/commands/render_command.rb +6 -1
  16. data/lib/elkrb/commands/validate_command.rb +146 -91
  17. data/lib/elkrb/errors.rb +20 -1
  18. data/lib/elkrb/format_sniffer.rb +362 -0
  19. data/lib/elkrb/geometry/bezier.rb +14 -4
  20. data/lib/elkrb/geometry/point.rb +5 -6
  21. data/lib/elkrb/geometry/rectangle.rb +2 -2
  22. data/lib/elkrb/graph/deep_stringify_keys.rb +30 -0
  23. data/lib/elkrb/graph/edge.rb +89 -3
  24. data/lib/elkrb/graph/graph.rb +43 -15
  25. data/lib/elkrb/graph/label.rb +16 -2
  26. data/lib/elkrb/graph/node.rb +13 -2
  27. data/lib/elkrb/graph/node_constraints.rb +139 -8
  28. data/lib/elkrb/graph/normalize_option_keys.rb +39 -0
  29. data/lib/elkrb/graph/port.rb +29 -21
  30. data/lib/elkrb/graph/read_only_mapping.rb +21 -0
  31. data/lib/elkrb/graphviz_wrapper.rb +152 -44
  32. data/lib/elkrb/layout/algorithm_registry.rb +65 -8
  33. data/lib/elkrb/layout/algorithms/base_algorithm.rb +74 -74
  34. data/lib/elkrb/layout/algorithms/box.rb +47 -20
  35. data/lib/elkrb/layout/algorithms/disco.rb +150 -61
  36. data/lib/elkrb/layout/algorithms/fixed.rb +70 -10
  37. data/lib/elkrb/layout/algorithms/force.rb +145 -86
  38. data/lib/elkrb/layout/algorithms/layered/bk/aligner.rb +104 -0
  39. data/lib/elkrb/layout/algorithms/layered/bk/compactor.rb +87 -0
  40. data/lib/elkrb/layout/algorithms/layered/bk/view.rb +103 -0
  41. data/lib/elkrb/layout/algorithms/layered/bk_node_placer.rb +170 -0
  42. data/lib/elkrb/layout/algorithms/layered/components.rb +170 -0
  43. data/lib/elkrb/layout/algorithms/layered/crossing_minimizer.rb +306 -0
  44. data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +118 -49
  45. data/lib/elkrb/layout/algorithms/layered/discovery_order.rb +75 -0
  46. data/lib/elkrb/layout/algorithms/layered/dummy_slot.rb +31 -0
  47. data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +188 -48
  48. data/lib/elkrb/layout/algorithms/layered/layer_constraints.rb +82 -0
  49. data/lib/elkrb/layout/algorithms/layered/layer_sweep.rb +195 -0
  50. data/lib/elkrb/layout/algorithms/layered/node_placer.rb +214 -36
  51. data/lib/elkrb/layout/algorithms/layered/orthogonal/gap_solver.rb +219 -0
  52. data/lib/elkrb/layout/algorithms/layered/orthogonal/hyper_edge_segment.rb +152 -0
  53. data/lib/elkrb/layout/algorithms/layered/orthogonal/junction_points.rb +58 -0
  54. data/lib/elkrb/layout/algorithms/layered/orthogonal/segment_order.rb +134 -0
  55. data/lib/elkrb/layout/algorithms/layered/orthogonal_router.rb +253 -0
  56. data/lib/elkrb/layout/algorithms/layered/port_order.rb +188 -0
  57. data/lib/elkrb/layout/algorithms/layered/port_spread.rb +42 -0
  58. data/lib/elkrb/layout/algorithms/layered.rb +548 -13
  59. data/lib/elkrb/layout/algorithms/libavoid.rb +951 -187
  60. data/lib/elkrb/layout/algorithms/mrtree.rb +585 -65
  61. data/lib/elkrb/layout/algorithms/radial.rb +188 -29
  62. data/lib/elkrb/layout/algorithms/random.rb +131 -17
  63. data/lib/elkrb/layout/algorithms/rectpacking.rb +28 -40
  64. data/lib/elkrb/layout/algorithms/spore_compaction.rb +57 -100
  65. data/lib/elkrb/layout/algorithms/spore_overlap.rb +78 -84
  66. data/lib/elkrb/layout/algorithms/stress.rb +391 -84
  67. data/lib/elkrb/layout/algorithms/topdown_packing.rb +95 -31
  68. data/lib/elkrb/layout/algorithms/vertiflex.rb +12 -47
  69. data/lib/elkrb/layout/constraints/alignment_constraint.rb +49 -12
  70. data/lib/elkrb/layout/constraints/base_constraint.rb +3 -15
  71. data/lib/elkrb/layout/constraints/constraint_processor.rb +9 -23
  72. data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +22 -13
  73. data/lib/elkrb/layout/constraints/layer_constraint.rb +10 -33
  74. data/lib/elkrb/layout/constraints/relative_position_constraint.rb +94 -50
  75. data/lib/elkrb/layout/edge_router.rb +533 -337
  76. data/lib/elkrb/layout/hierarchical_processor.rb +19 -276
  77. data/lib/elkrb/layout/java_random.rb +59 -0
  78. data/lib/elkrb/layout/label_placer.rb +165 -157
  79. data/lib/elkrb/layout/layout_engine.rb +46 -23
  80. data/lib/elkrb/layout/node_index.rb +149 -0
  81. data/lib/elkrb/layout/polyomino/component_compactor.rb +138 -0
  82. data/lib/elkrb/layout/polyomino/footprint.rb +94 -0
  83. data/lib/elkrb/layout/polyomino/grid.rb +106 -0
  84. data/lib/elkrb/layout/polyomino/packer.rb +95 -0
  85. data/lib/elkrb/layout/polyomino/piece.rb +52 -0
  86. data/lib/elkrb/layout/port_constraint_processor.rb +131 -44
  87. data/lib/elkrb/layout/spore/body.rb +65 -0
  88. data/lib/elkrb/layout/spore/depth_first_compaction.rb +127 -0
  89. data/lib/elkrb/layout/spore/edge_cost.rb +69 -0
  90. data/lib/elkrb/layout/spore/gap.rb +76 -0
  91. data/lib/elkrb/layout/spore/geometry.rb +104 -0
  92. data/lib/elkrb/layout/spore/overlap_sweep.rb +90 -0
  93. data/lib/elkrb/layout/spore/placement.rb +57 -0
  94. data/lib/elkrb/layout/spore/rays.rb +119 -0
  95. data/lib/elkrb/layout/spore/spanning_tree.rb +53 -0
  96. data/lib/elkrb/layout/spore/triangulation.rb +159 -0
  97. data/lib/elkrb/layout_flags.rb +69 -0
  98. data/lib/elkrb/options/decimal.rb +50 -0
  99. data/lib/elkrb/options/element_options.rb +43 -0
  100. data/lib/elkrb/options/elk_padding.rb +20 -14
  101. data/lib/elkrb/options/k_vector.rb +6 -7
  102. data/lib/elkrb/options/k_vector_chain.rb +12 -20
  103. data/lib/elkrb/options/option_map.rb +85 -0
  104. data/lib/elkrb/options/registry.rb +335 -0
  105. data/lib/elkrb/options/resolver.rb +151 -0
  106. data/lib/elkrb/options/spellings.rb +62 -0
  107. data/lib/elkrb/options/unhonoured_report.rb +194 -0
  108. data/lib/elkrb/parsers/elkt/lexer.rb +309 -0
  109. data/lib/elkrb/parsers/elkt/parser.rb +424 -0
  110. data/lib/elkrb/parsers/elkt/resolver.rb +142 -0
  111. data/lib/elkrb/parsers/elkt/token.rb +40 -0
  112. data/lib/elkrb/parsers/elkt_parser.rb +32 -230
  113. data/lib/elkrb/serializers/dot_serializer.rb +342 -54
  114. data/lib/elkrb/serializers/elkt_serializer.rb +386 -155
  115. data/lib/elkrb/version.rb +1 -1
  116. data/lib/elkrb.rb +60 -61
  117. metadata +71 -49
  118. data/.rspec +0 -3
  119. data/.rubocop.yml +0 -11
  120. data/Gemfile +0 -13
  121. data/Rakefile +0 -64
  122. data/benchmarks/README.md +0 -172
  123. data/benchmarks/elkjs_benchmark.js +0 -140
  124. data/benchmarks/elkrb_benchmark.rb +0 -145
  125. data/benchmarks/fixtures/graphs.json +0 -10777
  126. data/benchmarks/generate_report.rb +0 -241
  127. data/benchmarks/generate_test_graphs.rb +0 -154
  128. data/benchmarks/results/elkrb_results.json +0 -280
  129. data/benchmarks/results/elkrb_summary.json +0 -285
  130. data/elkrb.gemspec +0 -39
  131. data/examples/dot_export_demo.rb +0 -133
  132. data/examples/hierarchical_graph.rb +0 -19
  133. data/examples/layout_constraints_demo.rb +0 -272
  134. data/examples/port_constraints_demo.rb +0 -291
  135. data/examples/self_loop_demo.rb +0 -391
  136. data/examples/simple_graph.rb +0 -50
  137. data/examples/spline_routing_demo.rb +0 -235
  138. data/lib/elkrb/graph/layout_options.rb +0 -247
  139. data/sig/elkrb/constraints.rbs +0 -114
  140. data/sig/elkrb/geometry.rbs +0 -61
  141. data/sig/elkrb/graph.rbs +0 -112
  142. data/sig/elkrb/layout.rbs +0 -107
  143. data/sig/elkrb/options.rbs +0 -81
  144. data/sig/elkrb.rbs +0 -32
@@ -1,34 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../spore/placement"
4
+ require_relative "../spore/edge_cost"
5
+ require_relative "../spore/triangulation"
6
+ require_relative "../spore/spanning_tree"
7
+ require_relative "../spore/depth_first_compaction"
8
+
3
9
  module Elkrb
4
10
  module Layout
5
11
  module Algorithms
6
- # SPOrE Compaction algorithm
12
+ # SPOrE compaction ("ShrinkTree"): closes the gaps in an overlap-free
13
+ # layout without changing its topology.
14
+ #
15
+ # The node centres are joined by a Delaunay triangulation, a spanning
16
+ # tree of it is grown from the root node, and the tree is compacted
17
+ # depth first: each subtree slides toward its parent until it would
18
+ # touch another node.
7
19
  #
8
- # Compacts the layout by removing whitespace while preserving
9
- # the relative ordering and structure of nodes.
20
+ # Ties between equally cheap tree edges are broken by discovery order,
21
+ # where Java ELK breaks them by hash-set order. Nodes that start on
22
+ # the same point are nudged apart at random, as Java does.
10
23
  class SporeCompaction < BaseAlgorithm
24
+ include Spore::Placement
25
+
11
26
  def layout_flat(graph, _options = {})
12
27
  return graph if graph.children.empty?
13
28
 
14
- # Compact in both directions
15
- direction = graph.layout_options&.[]("spore.compactionDirection") || "both"
16
- min_spacing = graph.layout_options&.[]("spore.nodeSpacing") || 10.0
17
-
18
- case direction
19
- when "horizontal"
20
- compact_horizontal(graph.children, min_spacing)
21
- when "vertical"
22
- compact_vertical(graph.children, min_spacing)
23
- else
24
- compact_horizontal(graph.children, min_spacing)
25
- compact_vertical(graph.children, min_spacing)
26
- end
29
+ self.class.normalize_nil_positions(graph.children)
27
30
 
28
- # Normalize to start at origin
29
- normalize_positions(graph.children)
31
+ spacing = node_gap(graph)
32
+ bodies = build_bodies(graph.children, spacing)
33
+ compact(bodies, graph) if bodies.size > 1
30
34
 
31
- # Apply padding
35
+ bodies.each { |body| place(body) }
32
36
  apply_padding(graph)
33
37
 
34
38
  graph
@@ -36,102 +40,55 @@ module Elkrb
36
40
 
37
41
  private
38
42
 
39
- def compact_horizontal(nodes, min_spacing)
40
- # Sort nodes by x coordinate
41
- sorted_nodes = nodes.sort_by(&:x)
42
-
43
- # Compact from left to right
44
- sorted_nodes.each_with_index do |node, index|
45
- next if index.zero?
46
-
47
- # Find the rightmost x position among nodes to the left
48
- # that don't vertically overlap with current node
49
- max_left_x = find_max_left_x(node, sorted_nodes[0...index],
50
- min_spacing)
51
-
52
- # Move node left if there's space
53
- if max_left_x && max_left_x < node.x
54
- node.x = max_left_x
55
- end
56
- end
43
+ # spore.nodeSpacing, else ELK's own spacing id, else ELK's default.
44
+ def node_gap(graph)
45
+ resolver.get("spore.nodeSpacing", graph, default: nil) ||
46
+ resolver.get("elk.spacing.nodeNode", graph,
47
+ default: DEFAULT_SPACING)
57
48
  end
58
49
 
59
- def find_max_left_x(node, left_nodes, min_spacing)
60
- # Find nodes that vertically overlap with current node
61
- overlapping = left_nodes.select do |left_node|
62
- vertically_overlaps?(node, left_node)
63
- end
64
-
65
- return 0.0 if overlapping.empty?
66
-
67
- # Find the rightmost position among overlapping nodes
68
- rightmost = overlapping.map { |n| n.x + n.width }.max
69
- rightmost + min_spacing
50
+ def compact(bodies, graph)
51
+ root = select_root(bodies, graph)
52
+ Spore::DepthFirstCompaction.compact(grow_tree(bodies, root, graph),
53
+ mode: mode(graph))
70
54
  end
71
55
 
72
- def compact_vertical(nodes, min_spacing)
73
- # Sort nodes by y coordinate
74
- sorted_nodes = nodes.sort_by(&:y)
75
-
76
- # Compact from top to bottom
77
- sorted_nodes.each_with_index do |node, index|
78
- next if index.zero?
79
-
80
- # Find the bottommost y position among nodes above
81
- # that don't horizontally overlap with current node
82
- max_top_y = find_max_top_y(node, sorted_nodes[0...index],
83
- min_spacing)
56
+ def select_root(bodies, graph)
57
+ return most_central(bodies, graph) unless
58
+ resolver.get("elk.processingOrder.rootSelection", graph) == "FIXED"
84
59
 
85
- # Move node up if there's space
86
- if max_top_y && max_top_y < node.y
87
- node.y = max_top_y
88
- end
89
- end
60
+ wanted = resolver.get("elk.processingOrder.preferredRoot", graph)
61
+ named = bodies.select { |body| body.source.id == wanted } if wanted
62
+ named&.last || bodies.first
90
63
  end
91
64
 
92
- def find_max_top_y(node, top_nodes, min_spacing)
93
- # Find nodes that horizontally overlap with current node
94
- overlapping = top_nodes.select do |top_node|
95
- horizontally_overlaps?(node, top_node)
65
+ def grow_tree(bodies, root, graph)
66
+ cost = Spore::EdgeCost.for(
67
+ resolver.get("elk.processingOrder.spanningTreeCostFunction", graph),
68
+ root,
69
+ )
70
+ sign = maximum_tree?(graph) ? -1 : 1
71
+ Spore::SpanningTree.build(triangulated_edges(bodies), root) do |a, b|
72
+ sign * cost.call(a, b)
96
73
  end
97
-
98
- return 0.0 if overlapping.empty?
99
-
100
- # Find the bottommost position among overlapping nodes
101
- bottommost = overlapping.map { |n| n.y + n.height }.max
102
- bottommost + min_spacing
103
74
  end
104
75
 
105
- def vertically_overlaps?(node1, node2)
106
- top1 = node1.y
107
- bottom1 = node1.y + node1.height
108
- top2 = node2.y
109
- bottom2 = node2.y + node2.height
110
-
111
- !(bottom1 <= top2 || bottom2 <= top1)
76
+ def maximum_tree?(graph)
77
+ resolver.get("elk.processingOrder.treeConstruction", graph) ==
78
+ "MAXIMUM_SPANNING_TREE"
112
79
  end
113
80
 
114
- def horizontally_overlaps?(node1, node2)
115
- left1 = node1.x
116
- right1 = node1.x + node1.width
117
- left2 = node2.x
118
- right2 = node2.x + node2.width
119
-
120
- !(right1 <= left2 || right2 <= left1)
81
+ def triangulated_edges(bodies)
82
+ by_origin = bodies.to_h { |body| [body.origin, body] }
83
+ Spore::Triangulation.triangulate(by_origin.keys)
84
+ .map { |u, v| [by_origin[u], by_origin[v]] }
121
85
  end
122
86
 
123
- def normalize_positions(nodes)
124
- return if nodes.empty?
87
+ def mode(graph)
88
+ direction = resolver.get("spore.compactionDirection", graph)
89
+ return direction.to_sym unless direction == "both"
125
90
 
126
- # Find minimum x and y
127
- min_x = nodes.map(&:x).min
128
- min_y = nodes.map(&:y).min
129
-
130
- # Shift all nodes to start at origin
131
- nodes.each do |node|
132
- node.x -= min_x
133
- node.y -= min_y
134
- end
91
+ resolver.get("elk.compaction.orthogonal", graph) ? :orthogonal : :free
135
92
  end
136
93
  end
137
94
  end
@@ -1,28 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../spore/placement"
4
+ require_relative "../spore/edge_cost"
5
+ require_relative "../spore/overlap_sweep"
6
+ require_relative "../spore/triangulation"
7
+ require_relative "../spore/spanning_tree"
8
+
3
9
  module Elkrb
4
10
  module Layout
5
11
  module Algorithms
6
- # SPOrE Overlap Removal algorithm
12
+ # SPOrE overlap removal ("Node Overlap Removal by Growing a Tree").
13
+ #
14
+ # Each pass sweeps for overlapping pairs, joins the node centres by a
15
+ # minimum spanning tree over the overlap edges plus a Delaunay
16
+ # triangulation, then walks the tree from the most central node and
17
+ # stretches every tree edge just far enough that the two rectangles
18
+ # stop overlapping. Passes repeat until a pass stretches nothing or
19
+ # the cap is reached.
7
20
  #
8
- # Removes node overlaps while preserving the overall structure
9
- # of the layout by applying constrained positioning.
21
+ # Ties between equally cheap tree edges are broken by discovery order,
22
+ # where Java ELK breaks them by hash-set order.
10
23
  class SporeOverlap < BaseAlgorithm
24
+ include Spore::Placement
25
+
11
26
  def layout_flat(graph, _options = {})
12
27
  return graph if graph.children.empty?
13
28
 
14
- # Iteratively remove overlaps
15
- max_iterations = graph.layout_options&.[]("spore.maxIterations") || 50
16
- min_spacing = graph.layout_options&.[]("spore.nodeSpacing") || 10.0
29
+ self.class.normalize_nil_positions(graph.children)
17
30
 
18
- max_iterations.times do
19
- overlaps = find_overlaps(graph.children, min_spacing)
20
- break if overlaps.empty?
31
+ max_iterations = resolver.get("spore.maxIterations", graph)
32
+ spacing = resolver.get("spore.nodeSpacing", graph,
33
+ default: DEFAULT_SPACING)
21
34
 
22
- resolve_overlaps(overlaps, min_spacing)
23
- end
35
+ bodies = build_bodies(graph.children, spacing)
36
+ root = most_central(bodies, graph)
37
+ remove_overlaps(bodies, root, max_iterations)
38
+ warn_if_overlaps_remain(bodies, max_iterations)
24
39
 
25
- # Apply padding
40
+ bodies.each { |body| place(body) }
26
41
  apply_padding(graph)
27
42
 
28
43
  graph
@@ -30,86 +45,65 @@ module Elkrb
30
45
 
31
46
  private
32
47
 
33
- def find_overlaps(nodes, min_spacing)
34
- overlaps = []
35
-
36
- nodes.each_with_index do |node1, i|
37
- nodes[(i + 1)..].each do |node2|
38
- if overlapping?(node1, node2, min_spacing)
39
- overlaps << [node1, node2]
40
- end
41
- end
48
+ def remove_overlaps(bodies, root, max_iterations)
49
+ max_iterations.times do
50
+ pairs = Spore::OverlapSweep.pairs(bodies)
51
+ break if pairs.empty?
52
+
53
+ tree = Spore::SpanningTree.build(
54
+ tree_edges(bodies, pairs), root
55
+ ) { |first, second| Spore::EdgeCost.inverted_overlap(first, second) }
56
+ grown = grow(tree)
57
+ bodies.each(&:rebase)
58
+ break unless grown
42
59
  end
43
-
44
- overlaps
45
60
  end
46
61
 
47
- def overlapping?(node1, node2, min_spacing)
48
- # Calculate bounding boxes with spacing
49
- left1 = node1.x - (min_spacing / 2.0)
50
- right1 = node1.x + node1.width + (min_spacing / 2.0)
51
- top1 = node1.y - (min_spacing / 2.0)
52
- bottom1 = node1.y + node1.height + (min_spacing / 2.0)
62
+ def tree_edges(bodies, pairs)
63
+ by_origin = bodies.to_h { |body| [body.origin, body] }
64
+ triangulated = Spore::Triangulation.triangulate(by_origin.keys)
65
+ .map { |u, v| [by_origin[u], by_origin[v]] }
66
+ (pairs + triangulated).uniq { |edge| edge.map(&:object_id).sort }
67
+ end
53
68
 
54
- left2 = node2.x - (min_spacing / 2.0)
55
- right2 = node2.x + node2.width + (min_spacing / 2.0)
56
- top2 = node2.y - (min_spacing / 2.0)
57
- bottom2 = node2.y + node2.height + (min_spacing / 2.0)
69
+ # Walks the tree from the root, moving each child to where its
70
+ # parent's move put it and then stretching the edge to the parent.
71
+ # @return [Boolean] whether any edge needed stretching
72
+ def grow(tree)
73
+ stretched = false
74
+ pending = [tree]
75
+ until pending.empty?
76
+ parent = pending.pop
77
+ parent.children.each do |child|
78
+ stretched |= stretch(parent.body, child.body) > 1.0
79
+ end
80
+ pending.concat(parent.children.reverse)
81
+ end
82
+ stretched
83
+ end
58
84
 
59
- # Check for overlap
60
- !(right1 <= left2 || left1 >= right2 || bottom1 <= top2 || top1 >= bottom2)
85
+ # @return [Float] the factor the edge was stretched by
86
+ def stretch(parent, child)
87
+ child.translate(parent.center_x - parent.origin_x,
88
+ parent.center_y - parent.origin_y)
89
+ factor = Spore::Geometry.overlap(parent, child)
90
+ child.center_at(
91
+ parent.center_x + ((child.origin_x - parent.origin_x) * factor),
92
+ parent.center_y + ((child.origin_y - parent.origin_y) * factor),
93
+ )
94
+ factor
61
95
  end
62
96
 
63
- def resolve_overlaps(overlaps, min_spacing)
64
- overlaps.each do |node1, node2|
65
- # Calculate overlap amounts
66
- center1_x = node1.x + (node1.width / 2.0)
67
- center1_y = node1.y + (node1.height / 2.0)
68
- center2_x = node2.x + (node2.width / 2.0)
69
- center2_y = node2.y + (node2.height / 2.0)
70
-
71
- dx = center2_x - center1_x
72
- dy = center2_y - center1_y
73
-
74
- # Calculate minimum required distance
75
- min_dist_x = ((node1.width + node2.width) / 2.0) + min_spacing
76
- min_dist_y = ((node1.height + node2.height) / 2.0) + min_spacing
77
-
78
- # Determine movement direction
79
- if dx.abs > dy.abs
80
- # Move horizontally
81
- if dx.positive?
82
- # node2 is to the right
83
- overlap = min_dist_x - dx
84
- if overlap.positive?
85
- node2.x += overlap / 2.0
86
- node1.x -= overlap / 2.0
87
- end
88
- else
89
- # node2 is to the left
90
- overlap = min_dist_x + dx
91
- if overlap.positive?
92
- node2.x -= overlap / 2.0
93
- node1.x += overlap / 2.0
94
- end
95
- end
96
- elsif dy.positive?
97
- # Move vertically
98
- overlap = min_dist_y - dy
99
- if overlap.positive?
100
- node2.y += overlap / 2.0
101
- node1.y -= overlap / 2.0
102
- end
103
- # node2 is below
104
- else
105
- # node2 is above
106
- overlap = min_dist_y + dy
107
- if overlap.positive?
108
- node2.y -= overlap / 2.0
109
- node1.y += overlap / 2.0
110
- end
111
- end
97
+ def warn_if_overlaps_remain(bodies, max_iterations)
98
+ # Sides that only touch can still sweep as overlapping through
99
+ # rounding, so the warning counts real intersections only.
100
+ remaining = Spore::OverlapSweep.pairs(bodies).select do |pair|
101
+ Spore::Geometry.intersect?(*pair)
112
102
  end
103
+ return if remaining.empty?
104
+
105
+ warn "SporeOverlap: max_iterations (#{max_iterations}) exhausted " \
106
+ "with #{remaining.size} overlap(s) still remaining"
113
107
  end
114
108
  end
115
109
  end