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.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +364 -0
- data/LICENSE +25 -0
- data/README.adoc +695 -557
- data/exe/elkrb +36 -2
- data/lib/elkrb/atomic_destination.rb +53 -0
- data/lib/elkrb/best_effort_write.rb +49 -0
- data/lib/elkrb/cli/layout_flag_options.rb +54 -0
- data/lib/elkrb/cli.rb +155 -82
- data/lib/elkrb/command_resolver.rb +186 -0
- data/lib/elkrb/commands/batch_command.rb +28 -5
- data/lib/elkrb/commands/convert_command.rb +22 -52
- data/lib/elkrb/commands/diagram_command.rb +234 -102
- data/lib/elkrb/commands/options_command.rb +87 -0
- data/lib/elkrb/commands/render_command.rb +6 -1
- data/lib/elkrb/commands/validate_command.rb +146 -91
- data/lib/elkrb/errors.rb +20 -1
- data/lib/elkrb/format_sniffer.rb +362 -0
- data/lib/elkrb/geometry/bezier.rb +14 -4
- data/lib/elkrb/geometry/point.rb +5 -6
- data/lib/elkrb/geometry/rectangle.rb +2 -2
- data/lib/elkrb/graph/deep_stringify_keys.rb +30 -0
- data/lib/elkrb/graph/edge.rb +89 -3
- data/lib/elkrb/graph/graph.rb +43 -15
- data/lib/elkrb/graph/label.rb +16 -2
- data/lib/elkrb/graph/node.rb +13 -2
- data/lib/elkrb/graph/node_constraints.rb +139 -8
- data/lib/elkrb/graph/normalize_option_keys.rb +39 -0
- data/lib/elkrb/graph/port.rb +29 -21
- data/lib/elkrb/graph/read_only_mapping.rb +21 -0
- data/lib/elkrb/graphviz_wrapper.rb +152 -44
- data/lib/elkrb/layout/algorithm_registry.rb +65 -8
- data/lib/elkrb/layout/algorithms/base_algorithm.rb +74 -74
- data/lib/elkrb/layout/algorithms/box.rb +47 -20
- data/lib/elkrb/layout/algorithms/disco.rb +150 -61
- data/lib/elkrb/layout/algorithms/fixed.rb +70 -10
- data/lib/elkrb/layout/algorithms/force.rb +145 -86
- data/lib/elkrb/layout/algorithms/layered/bk/aligner.rb +104 -0
- data/lib/elkrb/layout/algorithms/layered/bk/compactor.rb +87 -0
- data/lib/elkrb/layout/algorithms/layered/bk/view.rb +103 -0
- data/lib/elkrb/layout/algorithms/layered/bk_node_placer.rb +170 -0
- data/lib/elkrb/layout/algorithms/layered/components.rb +170 -0
- data/lib/elkrb/layout/algorithms/layered/crossing_minimizer.rb +306 -0
- data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +118 -49
- data/lib/elkrb/layout/algorithms/layered/discovery_order.rb +75 -0
- data/lib/elkrb/layout/algorithms/layered/dummy_slot.rb +31 -0
- data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +188 -48
- data/lib/elkrb/layout/algorithms/layered/layer_constraints.rb +82 -0
- data/lib/elkrb/layout/algorithms/layered/layer_sweep.rb +195 -0
- data/lib/elkrb/layout/algorithms/layered/node_placer.rb +214 -36
- data/lib/elkrb/layout/algorithms/layered/orthogonal/gap_solver.rb +219 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/hyper_edge_segment.rb +152 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/junction_points.rb +58 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/segment_order.rb +134 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal_router.rb +253 -0
- data/lib/elkrb/layout/algorithms/layered/port_order.rb +188 -0
- data/lib/elkrb/layout/algorithms/layered/port_spread.rb +42 -0
- data/lib/elkrb/layout/algorithms/layered.rb +548 -13
- data/lib/elkrb/layout/algorithms/libavoid.rb +951 -187
- data/lib/elkrb/layout/algorithms/mrtree.rb +585 -65
- data/lib/elkrb/layout/algorithms/radial.rb +188 -29
- data/lib/elkrb/layout/algorithms/random.rb +131 -17
- data/lib/elkrb/layout/algorithms/rectpacking.rb +28 -40
- data/lib/elkrb/layout/algorithms/spore_compaction.rb +57 -100
- data/lib/elkrb/layout/algorithms/spore_overlap.rb +78 -84
- data/lib/elkrb/layout/algorithms/stress.rb +391 -84
- data/lib/elkrb/layout/algorithms/topdown_packing.rb +95 -31
- data/lib/elkrb/layout/algorithms/vertiflex.rb +12 -47
- data/lib/elkrb/layout/constraints/alignment_constraint.rb +49 -12
- data/lib/elkrb/layout/constraints/base_constraint.rb +3 -15
- data/lib/elkrb/layout/constraints/constraint_processor.rb +9 -23
- data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +22 -13
- data/lib/elkrb/layout/constraints/layer_constraint.rb +10 -33
- data/lib/elkrb/layout/constraints/relative_position_constraint.rb +94 -50
- data/lib/elkrb/layout/edge_router.rb +533 -337
- data/lib/elkrb/layout/hierarchical_processor.rb +19 -276
- data/lib/elkrb/layout/java_random.rb +59 -0
- data/lib/elkrb/layout/label_placer.rb +165 -157
- data/lib/elkrb/layout/layout_engine.rb +46 -23
- data/lib/elkrb/layout/node_index.rb +149 -0
- data/lib/elkrb/layout/polyomino/component_compactor.rb +138 -0
- data/lib/elkrb/layout/polyomino/footprint.rb +94 -0
- data/lib/elkrb/layout/polyomino/grid.rb +106 -0
- data/lib/elkrb/layout/polyomino/packer.rb +95 -0
- data/lib/elkrb/layout/polyomino/piece.rb +52 -0
- data/lib/elkrb/layout/port_constraint_processor.rb +131 -44
- data/lib/elkrb/layout/spore/body.rb +65 -0
- data/lib/elkrb/layout/spore/depth_first_compaction.rb +127 -0
- data/lib/elkrb/layout/spore/edge_cost.rb +69 -0
- data/lib/elkrb/layout/spore/gap.rb +76 -0
- data/lib/elkrb/layout/spore/geometry.rb +104 -0
- data/lib/elkrb/layout/spore/overlap_sweep.rb +90 -0
- data/lib/elkrb/layout/spore/placement.rb +57 -0
- data/lib/elkrb/layout/spore/rays.rb +119 -0
- data/lib/elkrb/layout/spore/spanning_tree.rb +53 -0
- data/lib/elkrb/layout/spore/triangulation.rb +159 -0
- data/lib/elkrb/layout_flags.rb +69 -0
- data/lib/elkrb/options/decimal.rb +50 -0
- data/lib/elkrb/options/element_options.rb +43 -0
- data/lib/elkrb/options/elk_padding.rb +20 -14
- data/lib/elkrb/options/k_vector.rb +6 -7
- data/lib/elkrb/options/k_vector_chain.rb +12 -20
- data/lib/elkrb/options/option_map.rb +85 -0
- data/lib/elkrb/options/registry.rb +335 -0
- data/lib/elkrb/options/resolver.rb +151 -0
- data/lib/elkrb/options/spellings.rb +62 -0
- data/lib/elkrb/options/unhonoured_report.rb +194 -0
- data/lib/elkrb/parsers/elkt/lexer.rb +309 -0
- data/lib/elkrb/parsers/elkt/parser.rb +424 -0
- data/lib/elkrb/parsers/elkt/resolver.rb +142 -0
- data/lib/elkrb/parsers/elkt/token.rb +40 -0
- data/lib/elkrb/parsers/elkt_parser.rb +32 -230
- data/lib/elkrb/serializers/dot_serializer.rb +342 -54
- data/lib/elkrb/serializers/elkt_serializer.rb +386 -155
- data/lib/elkrb/version.rb +1 -1
- data/lib/elkrb.rb +60 -61
- metadata +71 -49
- data/.rspec +0 -3
- data/.rubocop.yml +0 -11
- data/Gemfile +0 -13
- data/Rakefile +0 -64
- data/benchmarks/README.md +0 -172
- data/benchmarks/elkjs_benchmark.js +0 -140
- data/benchmarks/elkrb_benchmark.rb +0 -145
- data/benchmarks/fixtures/graphs.json +0 -10777
- data/benchmarks/generate_report.rb +0 -241
- data/benchmarks/generate_test_graphs.rb +0 -154
- data/benchmarks/results/elkrb_results.json +0 -280
- data/benchmarks/results/elkrb_summary.json +0 -285
- data/elkrb.gemspec +0 -39
- data/examples/dot_export_demo.rb +0 -133
- data/examples/hierarchical_graph.rb +0 -19
- data/examples/layout_constraints_demo.rb +0 -272
- data/examples/port_constraints_demo.rb +0 -291
- data/examples/self_loop_demo.rb +0 -391
- data/examples/simple_graph.rb +0 -50
- data/examples/spline_routing_demo.rb +0 -235
- data/lib/elkrb/graph/layout_options.rb +0 -247
- data/sig/elkrb/constraints.rbs +0 -114
- data/sig/elkrb/geometry.rbs +0 -61
- data/sig/elkrb/graph.rbs +0 -112
- data/sig/elkrb/layout.rbs +0 -107
- data/sig/elkrb/options.rbs +0 -81
- 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
|
|
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
|
-
#
|
|
9
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
31
|
+
spacing = node_gap(graph)
|
|
32
|
+
bodies = build_bodies(graph.children, spacing)
|
|
33
|
+
compact(bodies, graph) if bodies.size > 1
|
|
30
34
|
|
|
31
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
124
|
-
|
|
87
|
+
def mode(graph)
|
|
88
|
+
direction = resolver.get("spore.compactionDirection", graph)
|
|
89
|
+
return direction.to_sym unless direction == "both"
|
|
125
90
|
|
|
126
|
-
|
|
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
|
|
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
|
-
#
|
|
9
|
-
#
|
|
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
|
-
|
|
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.
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
max_iterations = resolver.get("spore.maxIterations", graph)
|
|
32
|
+
spacing = resolver.get("spore.nodeSpacing", graph,
|
|
33
|
+
default: DEFAULT_SPACING)
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|