contrek 1.0.9 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15c162c3876502f8e8710ce261825dfe7fa66e1b600db084763e02ef70876a37
4
- data.tar.gz: 32203f85040726d71ac519aac811d4078474c85dcd7c7b08227f1cea7e3b485f
3
+ metadata.gz: 1a5b664225e4cd276127b4d2861bb74382dc178cbdfd23ab36afd169c46eaca0
4
+ data.tar.gz: '06601846595b647920216a6035fc00885972416222114a8f306b0f3219cafaa5'
5
5
  SHA512:
6
- metadata.gz: c9f8d2a44f087f96cfeacc8df8dec0973bf301f770ae45e82e6bf7d01902546c280c0446ee9c281c850a8e7c8965309e20d1a723ec269e60a99d386c288af0e7
7
- data.tar.gz: 4d2c5c834fca24731866d0a56cc64d4aeac12437fac11b78389c38f7fbed9e637c785eb2a3357fb5c90a5fc257b2d010fe36cac2f84db3888f2b311276c42895
6
+ metadata.gz: baf09342bd36fb5c0f73d6fa9a679fd7eae42a26ba419832964da24cbd27ff8a58d5725d82a87cf0458351d2c8acc3ed6d18690f31d91b69f77c7de91c891ea4
7
+ data.tar.gz: 19b61c4937cf4c944de9312114e8be41dfd2b524d1cb9ff34449e567d007870b318d71b0c4f90dda5257ae9e66c949ac58b0e209ddc5c18887f23fb7df65af68
data/CHANGELOG.md CHANGED
@@ -34,3 +34,9 @@ All notable changes to this project will be documented in this file.
34
34
  ### Added
35
35
  - **CMake Integration**: The library is now fully modularized via CMake, enabling seamless integration as a dependency in external C++ projects.
36
36
  - Multithreading Algorithm Optimization.
37
+
38
+ ## [1.1.0] - 2026-02-08
39
+ ### Changes
40
+ - Resolved connectivity issues between internal zones and the outer boundary.
41
+ - Refined the internal parts stitching algorithm (sew method) for better performance.
42
+ - Miscellaneous optimizations and improvements.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contrek (1.0.9)
4
+ contrek (1.1.0)
5
5
  chunky_png (~> 1.4)
6
6
  concurrent-ruby (~> 1.3.5)
7
7
  rice (= 4.5.0)
data/README.md CHANGED
@@ -99,25 +99,25 @@ Regarding multithreading:
99
99
 
100
100
  - The treemap option is currently ignored (multithreaded treemap support will be introduced in upcoming revisions).
101
101
 
102
- By not declaring native option CPP Multithreading optimized code is used. In the above example a [105 MP image](spec/files/images/sample_10240x10240.png) is examined by 2 threads working on 2 tiles (total compute time about 1.66 secs with image load).
102
+ By not declaring native option CPP Multithreading optimized code is used. In the above example a [105 MP image](spec/files/images/sample_10240x10240.png) is examined by 4 threads working on 4 tiles (total compute time about 1.53 secs with image load).
103
103
 
104
104
  ```ruby
105
105
  result = Contrek.contour!(
106
106
  png_file_path: "./spec/files/images/sample_10240x10240.png",
107
107
  options: {
108
- number_of_threads: 2,
108
+ number_of_threads: 4,
109
109
  class: "value_not_matcher",
110
110
  color: {r: 255, g: 255, b: 255, a: 255},
111
- finder: {number_of_tiles: 2, compress: {uniq: true}}
111
+ finder: {number_of_tiles: 4, compress: {uniq: true}}
112
112
  }
113
113
  )
114
114
  puts result.metadata[:benchmarks].inspect
115
115
 
116
- { "compress"=>13.423765,
117
- "init"=>612.654121,
118
- "inner"=>14.8930669,
119
- "outer"=>33.0693249,
120
- "total"=>626.0778879
116
+ { compress: 13.0815,
117
+ init: 453.245,
118
+ inner: 27.0072,
119
+ outer: 66.9162,
120
+ total: 466.326
121
121
  }
122
122
 
123
123
  ```
@@ -26,8 +26,8 @@ void run_test() {
26
26
 
27
27
  int main() {
28
28
  Contrek::Config cfg;
29
- cfg.threads = 2;
30
- cfg.tiles = 2;
29
+ cfg.threads = 4;
30
+ cfg.tiles = 4;
31
31
  cfg.compress_unique = true;
32
32
 
33
33
  CpuTimer cpu_timer;
@@ -24,39 +24,23 @@ Cursor::~Cursor() {
24
24
 
25
25
  Sequence* Cursor::join_outers()
26
26
  { Polyline* outer_polyline = shape->outer_polyline;
27
- this->polylines_sequence.push_back(outer_polyline);
27
+ this->shapes_sequence.push_back(this->shape);
28
+ this->shapes_sequence_lookup.insert(this->shape);
28
29
  Sequence* outer_joined_polyline = new Sequence();
29
30
  this->allocated_sequences.push_back(outer_joined_polyline);
30
- int counter = 0;
31
31
  std::vector<Part*> all_parts;
32
- std::vector<Shape*> shapes;
33
- shapes.push_back(this->shape);
34
32
  this->traverse_outer(outer_polyline->parts().front(),
35
33
  all_parts,
36
- this->polylines_sequence,
37
- shapes,
38
- outer_joined_polyline, counter);
34
+ this->shapes_sequence,
35
+ outer_joined_polyline);
39
36
 
40
37
  if (*outer_joined_polyline->head->payload == *outer_joined_polyline->tail->payload &&
41
38
  this->cluster.tiles().front()->left() &&
42
39
  this->cluster.tiles().back()->right()) outer_joined_polyline->pop();
43
40
 
44
- // TODO(ema): optimize
45
- std::unordered_set<Polyline*> seen;
46
- std::vector<Polyline*> result;
47
- for (Polyline* x : polylines_sequence) {
48
- if (seen.insert(x).second) {
49
- result.push_back(x);
50
- }
51
- }
52
- polylines_sequence = result;
53
-
54
- for (Polyline* polyline : polylines_sequence) {
55
- polyline->turn_on(Polyline::TRACKED_OUTER);
56
- }
57
-
58
- for (Shape *shape : shapes)
59
- { if (shape == outer_polyline->shape) {
41
+ for (Shape* shape : shapes_sequence) {
42
+ shape->outer_polyline->turn_on(Polyline::TRACKED_OUTER);
43
+ if (shape == outer_polyline->shape) {
60
44
  continue;
61
45
  }
62
46
  orphan_inners_.insert(
@@ -70,22 +54,13 @@ Sequence* Cursor::join_outers()
70
54
 
71
55
  void Cursor::traverse_outer(Part* act_part,
72
56
  std::vector<Part*>& all_parts,
73
- std::vector<Polyline*>& polylines,
74
- std::vector<Shape*>& shapes,
75
- Sequence* outer_joined_polyline,
76
- int& counter) {
57
+ std::vector<Shape*>& shapes_sequence,
58
+ Sequence* outer_joined_polyline) {
77
59
  while (act_part != nullptr) {
78
- counter += 1;
79
-
80
60
  if (all_parts.empty() || all_parts.back() != act_part) {
81
61
  all_parts.push_back(act_part);
82
62
  }
83
63
 
84
- Shape* target_shape = act_part->polyline()->shape;
85
- if (std::find(shapes.begin(), shapes.end(), target_shape) == shapes.end()) {
86
- shapes.push_back(target_shape);
87
- }
88
-
89
64
  bool jumped_to_new_part = false;
90
65
  if (act_part->is(Part::EXCLUSIVE)) {
91
66
  if (act_part->size == 0) return;
@@ -123,7 +98,9 @@ void Cursor::traverse_outer(Part* act_part,
123
98
  }
124
99
  if (all_seam) break;
125
100
  }
126
- polylines.push_back(part->polyline()->shape->outer_polyline);
101
+ if (shapes_sequence_lookup.insert(part->polyline()->shape).second) {
102
+ shapes_sequence.push_back(part->polyline()->shape);
103
+ }
127
104
  part->next_position(new_position);
128
105
  act_part = part;
129
106
  jumped_to_new_part = true;
@@ -152,33 +129,40 @@ std::vector<Sequence*> Cursor::join_inners(Sequence* outer_seq) {
152
129
  { if (shape->outer_polyline->is_on(Polyline::TRACKED_OUTER) ||
153
130
  shape->outer_polyline->is_on(Polyline::TRACKED_INNER) ||
154
131
  !shape->outer_polyline->boundary() ||
155
- std::find(polylines_sequence.begin(), polylines_sequence.end(), shape->outer_polyline) != polylines_sequence.end()) {
132
+ std::find(shapes_sequence.begin(), shapes_sequence.end(), shape) != shapes_sequence.end()) {
156
133
  continue;
157
134
  }
158
135
  missing_shapes.push_back(shape);
159
136
  }
160
137
  }
161
138
 
162
- std::vector<Shape*> to_delay;
163
- to_delay = connect_missings(missing_shapes);
164
- while (!to_delay.empty()) {
165
- to_delay = connect_missings(to_delay);
139
+ if (missing_shapes.size() > 0) {
140
+ std::vector<Shape*> to_delay_shapes;
141
+ to_delay_shapes = connect_missings(this->shapes_sequence, missing_shapes);
142
+ if (!to_delay_shapes.empty())
143
+ { connect_missings(to_delay_shapes, missing_shapes);
144
+ while (!to_delay_shapes.empty()) {
145
+ to_delay_shapes = connect_missings(this->shapes_sequence, to_delay_shapes);
146
+ }
147
+ }
166
148
  }
167
149
 
168
150
  retme = collect_inner_sequences(outer_seq);
169
- for (Polyline* polyline : polylines_sequence) {
170
- polyline->turn_on(Polyline::TRACKED_INNER);
151
+ for (Shape* shape : shapes_sequence) {
152
+ shape->outer_polyline->turn_on(Polyline::TRACKED_INNER);
171
153
  }
172
154
  return(retme);
173
155
  }
174
156
 
175
- std::vector<Shape*> Cursor::connect_missings(std::vector<Shape*> missing_shapes) {
157
+ std::vector<Shape*> Cursor::connect_missings(std::vector<Shape*> shapes_sequence, std::vector<Shape*> missing_shapes) {
176
158
  std::vector<Shape*> delay_shapes;
177
159
 
178
- for (Polyline *polyline : polylines_sequence)
179
- { for (Shape *missing_shape : missing_shapes)
160
+ for (Shape* shape : shapes_sequence)
161
+ { Polyline* polyline = shape->outer_polyline;
162
+ for (Shape* missing_shape : missing_shapes)
180
163
  { Polyline* outer_polyline = missing_shape->outer_polyline;
181
- if ( outer_polyline->is_on(Polyline::TRACKED_OUTER) ||
164
+ if ( (polyline->mixed_tile_origin == false && outer_polyline->tile == polyline->tile) ||
165
+ outer_polyline->is_on(Polyline::TRACKED_OUTER) ||
182
166
  !polyline->vert_intersect(*outer_polyline)) continue;
183
167
  std::vector<std::pair<int, int>> intersection = polyline->intersection(outer_polyline);
184
168
  if (intersection.size() > 0)
@@ -218,6 +202,7 @@ std::vector<Shape*> Cursor::connect_missings(std::vector<Shape*> missing_shapes)
218
202
  orphan_inners_.push_back(sewn_sequence);
219
203
  }
220
204
  }
205
+ polyline->mixed_tile_origin = true;
221
206
  outer_polyline->clear();
222
207
  outer_polyline->turn_on(Polyline::TRACKED_OUTER);
223
208
  outer_polyline->turn_on(Polyline::TRACKED_INNER);
@@ -235,8 +220,9 @@ std::vector<Shape*> Cursor::connect_missings(std::vector<Shape*> missing_shapes)
235
220
  std::vector<Sequence*> Cursor::collect_inner_sequences(Sequence* outer_seq) {
236
221
  std::vector<Sequence*> return_sequences;
237
222
 
238
- for (Polyline* polyline : polylines_sequence)
239
- { for (Part* part : polyline->parts())
223
+ for (Shape* shape : shapes_sequence)
224
+ { Polyline* polyline = shape->outer_polyline;
225
+ for (Part* part : polyline->parts())
240
226
  { if (part->innerable())
241
227
  { std::vector<Part*> all_parts;
242
228
  Bounds bounds{
@@ -291,15 +277,17 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
291
277
  for (Part* dest_part : shape->outer_polyline->parts()) {
292
278
  if (dest_part->trasmuted || dest_part->is(Part::EXCLUSIVE)) continue;
293
279
  if (dest_part->intersect_part(act_part)) {
294
- std::vector<Point*> link_seq = duplicates_intersection(*dest_part, *act_part);
280
+ std::vector<EndPoint*> link_seq = duplicates_intersection(*dest_part, *act_part);
295
281
  if (!link_seq.empty()) {
296
282
  Part* ins_part = pool.acquire(Part::ADDED, act_part->polyline());
297
- for (Point* pos : link_seq) {
298
- this->cluster.positions_pool.emplace_back(this->cluster.hub(), pos);
283
+ for (EndPoint* pos : link_seq) {
284
+ this->cluster.positions_pool.emplace_back(pos);
299
285
  ins_part->add(&this->cluster.positions_pool.back());
300
286
  }
301
287
  all_parts.push_back(ins_part);
302
288
  }
289
+ shape->outer_polyline->turn_on(Polyline::TRACKED_OUTER);
290
+ shape->outer_polyline->turn_on(Polyline::TRACKED_INNER);
303
291
  act_part = dest_part->circular_next;
304
292
  jumped = true;
305
293
  break;
@@ -324,14 +312,13 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
324
312
  }
325
313
 
326
314
  template <typename T>
327
- std::vector<T*> difference_ptr(const std::vector<T*>& a, const std::vector<T*>& b)
315
+ std::vector<T*> difference_ptr(std::vector<T*>& a, std::vector<T*>& b)
328
316
  { std::vector<T*> result;
329
317
  for (T* item : a) {
330
- if (!item) continue;
331
318
  auto it = std::find_if(
332
319
  b.begin(), b.end(),
333
320
  [&](T* other) {
334
- return other && *other == *item;
321
+ return other == item;
335
322
  });
336
323
  if (it == b.end()) {
337
324
  result.push_back(item);
@@ -340,11 +327,13 @@ std::vector<T*> difference_ptr(const std::vector<T*>& a, const std::vector<T*>&
340
327
  return result;
341
328
  }
342
329
 
343
- std::vector<Point*> Cursor::duplicates_intersection(const Part& part_a, const Part& part_b) {
344
- std::vector<Point*> a1 = part_a.inverts ? part_a.remove_adjacent_pairs(part_a.to_vector()) : part_a.to_vector();
345
- std::vector<Point*> b1 = part_b.inverts ? part_b.remove_adjacent_pairs(part_b.to_vector()) : part_b.to_vector();
346
- std::vector<Point*> result = difference_ptr(a1, b1);
347
- std::vector<Point*> temp = difference_ptr(b1, a1);
330
+ std::vector<EndPoint*> Cursor::duplicates_intersection(Part& part_a, Part& part_b) {
331
+ auto a1 = part_a.to_endpoints();
332
+ auto b1 = part_b.to_endpoints();
333
+ if (part_a.inverts) a1 = Part::remove_adjacent_pairs(a1);
334
+ if (part_b.inverts) b1 = Part::remove_adjacent_pairs(b1);
335
+ std::vector<EndPoint*> result = difference_ptr(a1, b1);
336
+ std::vector<EndPoint*> temp = difference_ptr(b1, a1);
348
337
  result.insert(result.end(), temp.begin(), temp.end());
349
338
  return result;
350
339
  }
@@ -9,6 +9,7 @@
9
9
  #pragma once
10
10
  #include <list>
11
11
  #include <vector>
12
+ #include <unordered_set>
12
13
  #include "Sequence.h"
13
14
  #include "Cluster.h"
14
15
  #include "Shape.h"
@@ -31,17 +32,16 @@ class Cursor {
31
32
  Cluster& cluster;
32
33
  Shape* shape;
33
34
  std::vector<Sequence*> allocated_sequences;
34
- std::vector<Polyline*> polylines_sequence;
35
+ std::vector<Shape*> shapes_sequence;
36
+ std::unordered_set<Shape*> shapes_sequence_lookup;
35
37
  std::list<std::vector<Point*>> orphan_inners_;
36
38
  void traverse_outer(Part* act_part,
37
39
  std::vector<Part*>& all_parts,
38
- std::vector<Polyline*>& polylines,
39
- std::vector<Shape*>& shapes,
40
- Sequence* outer_joined_polyline,
41
- int& counter);
40
+ std::vector<Shape*>& shapes_sequence,
41
+ Sequence* outer_joined_polyline);
42
42
  std::vector<Sequence*> collect_inner_sequences(Sequence* outer_seq);
43
43
  void traverse_inner(Part* act_part, std::vector<Part*> &all_parts, Bounds& bounds);
44
- std::vector<Point*> duplicates_intersection(const Part& part_a, const Part& part_b);
44
+ std::vector<EndPoint*> duplicates_intersection(Part& part_a, Part& part_b);
45
45
  std::vector<std::vector<Point*>> combine(std::vector<std::vector<Point*>>& seqa, std::vector<std::vector<Point*>>& seqb);
46
- std::vector<Shape*> connect_missings(std::vector<Shape*> missing_shapes);
46
+ std::vector<Shape*> connect_missings(std::vector<Shape*> shapes_sequence, std::vector<Shape*> missing_shapes);
47
47
  };
@@ -14,9 +14,13 @@ class Part;
14
14
 
15
15
  class EndPoint {
16
16
  public:
17
- EndPoint() {}
17
+ EndPoint() : point_(nullptr) {}
18
+
19
+ void set_point(Point* p) { point_ = p; }
20
+ Point* get_point() const { return point_; }
18
21
  std::vector<Queueable<Point>*>& queues() { return queues_; }
19
22
  bool queues_include(Queueable<Point>* q) const;
20
23
  private:
24
+ Point* point_;
21
25
  std::vector<Queueable<Point>*> queues_;
22
26
  };
@@ -8,6 +8,7 @@
8
8
 
9
9
  #include "Part.h"
10
10
  #include <iostream>
11
+ #include <vector>
11
12
  #include "Polyline.h"
12
13
  #include "Tile.h"
13
14
  #include "Cluster.h"
@@ -69,3 +70,26 @@ bool Part::intersect_part(Part* other_part)
69
70
  });
70
71
  return(intersect);
71
72
  }
73
+
74
+ std::vector<EndPoint*> Part::to_endpoints() {
75
+ std::vector<EndPoint*> out;
76
+ QNode<Point>* current = head;
77
+ while (current) {
78
+ out.push_back((dynamic_cast<Position*>(current))->end_point());
79
+ current = current->next;
80
+ }
81
+ return out;
82
+ }
83
+
84
+ std::vector<EndPoint*> Part::remove_adjacent_pairs(const std::vector<EndPoint*>& input = {}) {
85
+ std::vector<EndPoint*> result;
86
+ result.reserve(input.size());
87
+ for (EndPoint* current : input) {
88
+ if (!result.empty() && result.back() == current) {
89
+ result.pop_back();
90
+ } else {
91
+ result.push_back(current);
92
+ }
93
+ }
94
+ return result;
95
+ }
@@ -10,12 +10,15 @@
10
10
  #include <cstdint>
11
11
  #include <string>
12
12
  #include <deque>
13
+ #include <vector>
13
14
  #include "Queueable.h"
14
15
  #include "Position.h"
16
+ #include "EndPoint.h"
15
17
  #include "../Node.h"
16
18
 
17
19
  class Polyline;
18
20
  class Position;
21
+ class EndPoint;
19
22
  class Part : public Queueable<Point> {
20
23
  public:
21
24
  enum Types : uint32_t {
@@ -42,6 +45,8 @@ class Part : public Queueable<Point> {
42
45
  void touch();
43
46
  bool intersect_part(Part* other_part);
44
47
  void set_polyline(Polyline* polyline) { this->polyline_ = polyline; }
48
+ std::vector<EndPoint*> to_endpoints();
49
+ static std::vector<EndPoint*> remove_adjacent_pairs(const std::vector<EndPoint*>& input);
45
50
 
46
51
  private:
47
52
  bool touched_ = false;
@@ -45,6 +45,7 @@ class Polyline : public Partitionable {
45
45
  bool is_empty();
46
46
  bool vert_intersect(Polyline& other);
47
47
  void reset_tracked_endpoints();
48
+ bool mixed_tile_origin = false;
48
49
 
49
50
  private:
50
51
  std::vector<Point*> raw_;
@@ -16,12 +16,18 @@ Position::Position(Hub* hub, Point* point)
16
16
  EndPoint* existing_ep = hub->get(key);
17
17
  if (existing_ep == nullptr)
18
18
  { end_point_ = hub->put(key, hub->spawn_end_point());
19
+ end_point_->set_point(point);
19
20
  } else {
20
21
  end_point_ = existing_ep;
21
22
  }
22
23
  }
23
24
  }
24
25
 
26
+ Position::Position(EndPoint* end_point)
27
+ : QNode<Point>(end_point->get_point())
28
+ { this->end_point_ = end_point;
29
+ }
30
+
25
31
  void Position::before_rem(Queueable<Point>* q) {
26
32
  if (this->end_point_ != nullptr) {
27
33
  auto& queues = this->end_point_->queues();
@@ -17,7 +17,8 @@ class Hub;
17
17
  class Position : public QNode<Point>{
18
18
  public:
19
19
  explicit Position(Hub* hub, Point* point);
20
- const EndPoint* end_point() const { return end_point_; }
20
+ explicit Position(EndPoint* end_point);
21
+ EndPoint* end_point() { return end_point_; }
21
22
  void before_rem(Queueable<Point>* q) override;
22
23
  void after_add(Queueable<Point>* q) override;
23
24
  private:
@@ -206,19 +206,4 @@ class Queueable {
206
206
  }
207
207
  return false;
208
208
  }
209
-
210
- std::vector<T*> remove_adjacent_pairs(const std::vector<T*>& input = {}) const {
211
- const std::vector<T*>& source = input.empty() ? to_vector() : input;
212
- std::vector<T*> result;
213
- result.reserve(source.size());
214
-
215
- for (T* current : source) {
216
- if (!result.empty() && *result.back() == *current) {
217
- result.pop_back();
218
- } else {
219
- result.push_back(current);
220
- }
221
- }
222
- return result;
223
- }
224
209
  };
@@ -1,8 +1,8 @@
1
1
  module Contrek
2
2
  module Bitmaps
3
3
  class CustomBitmap < PngBitmap
4
- def initialize(w:, h:)
5
- @image = ChunkyPNG::Image.new(w, h, ChunkyPNG::Color::TRANSPARENT)
4
+ def initialize(w:, h:, color: ChunkyPNG::Color::TRANSPARENT)
5
+ @image = ChunkyPNG::Image.new(w, h, color)
6
6
  end
7
7
  end
8
8
  end
@@ -3,11 +3,10 @@ module Contrek
3
3
  class Cursor
4
4
  attr_reader :orphan_inners
5
5
  def initialize(cluster:, shape:)
6
- @polylines_sequence = []
6
+ @shapes_sequence = Set.new([shape])
7
7
  @cluster = cluster
8
8
  @outer_polyline = shape.outer_polyline
9
9
  @orphan_inners = []
10
- @shapes = [shape]
11
10
  end
12
11
 
13
12
  def inspect
@@ -15,26 +14,22 @@ module Contrek
15
14
  end
16
15
 
17
16
  # Given the initial polyline, draw its outer boundary, possibly extending into
18
- # adjacent polylines, and then connect them. At the end, @polylines_sequence
17
+ # adjacent polylines, and then connect them. At the end, @shapes_sequence
19
18
  # contains the merged polylines. Returns a new resulting polyline.
20
19
  def join_outers!
21
20
  seq_log = []
22
- @polylines_sequence << @outer_polyline
23
21
 
24
22
  outer_joined_polyline = Sequence.new
25
-
26
23
  traverse_outer(@outer_polyline.parts.first,
27
24
  seq_log,
28
- @polylines_sequence,
29
- @shapes,
25
+ @shapes_sequence,
30
26
  outer_joined_polyline)
31
27
  outer_joined_polyline.pop! if outer_joined_polyline.head.payload == outer_joined_polyline.tail.payload &&
32
28
  @cluster.tiles.first.left? && @cluster.tiles.last.right?
33
29
 
34
- @polylines_sequence.uniq!
35
-
36
- @polylines_sequence.each { |c| c.turn_on(Polyline::TRACKED_OUTER) }
37
- (@shapes - [@outer_polyline.shape]).each do |shape|
30
+ @shapes_sequence.each do |shape|
31
+ shape.outer_polyline.turn_on(Polyline::TRACKED_OUTER)
32
+ next if shape == @outer_polyline.shape
38
33
  @orphan_inners += shape.inner_polylines
39
34
  shape.clear_inner!
40
35
  end
@@ -50,32 +45,41 @@ module Contrek
50
45
  next if shape.outer_polyline.on?(Polyline::TRACKED_OUTER) ||
51
46
  shape.outer_polyline.on?(Polyline::TRACKED_INNER) ||
52
47
  !shape.outer_polyline.boundary? ||
53
- @polylines_sequence.include?(shape.outer_polyline)
48
+ @shapes_sequence.include?(shape)
54
49
  missing_shapes << shape
55
50
  end
56
51
  end
57
- to_delay = connect_missings(missing_shapes)
58
- while to_delay.any?
59
- to_delay = connect_missings(to_delay)
52
+
53
+ if missing_shapes.any?
54
+ to_delay_shapes = connect_missings(@shapes_sequence, missing_shapes)
55
+ if to_delay_shapes.any?
56
+ connect_missings(to_delay_shapes, missing_shapes)
57
+ while to_delay_shapes.any?
58
+ to_delay_shapes = connect_missings(@shapes_sequence, to_delay_shapes)
59
+ end
60
+ end
60
61
  end
61
62
 
62
63
  retme = collect_inner_sequences(outer_seq)
63
64
 
64
- @polylines_sequence.each do |polyline|
65
- polyline.turn_on(Polyline::TRACKED_INNER)
65
+ @shapes_sequence.each do |shape|
66
+ shape.outer_polyline.turn_on(Polyline::TRACKED_INNER)
66
67
  end
67
68
  retme
68
69
  end
69
70
 
70
71
  private
71
72
 
72
- def connect_missings(missing_shapes)
73
+ def connect_missings(shapes_sequence, missing_shapes)
73
74
  delay_shapes = []
74
75
 
75
- @polylines_sequence.each do |polyline|
76
+ shapes_sequence.each do |shape|
77
+ polyline = shape.outer_polyline
76
78
  missing_shapes.each do |missing_shape|
77
79
  missing_outer_polyline = missing_shape.outer_polyline
78
- next if missing_outer_polyline.on?(Polyline::TRACKED_OUTER) ||
80
+ next if (polyline.mixed_tile_origin == false && missing_outer_polyline.tile == polyline.tile) || # accepts only other side ones
81
+ missing_outer_polyline.on?(Polyline::TRACKED_OUTER) ||
82
+ polyline == missing_outer_polyline ||
79
83
  !polyline.vert_intersect?(missing_outer_polyline)
80
84
 
81
85
  if (intersection = polyline.intersection(missing_outer_polyline)).any?
@@ -89,6 +93,7 @@ module Contrek
89
93
  @orphan_inners << sewn_sequence if sewn_sequence.size > 1 && sewn_sequence.map { |c| c[:x] }.uniq.size > 1 # segmenti non sono ammessi, solo aree
90
94
  end
91
95
  missing_outer_polyline.clear!
96
+ polyline.mixed_tile_origin = true
92
97
  missing_outer_polyline.turn_on(Polyline::TRACKED_OUTER)
93
98
  missing_outer_polyline.turn_on(Polyline::TRACKED_INNER)
94
99
  @orphan_inners += missing_shape.inner_polylines
@@ -100,9 +105,9 @@ module Contrek
100
105
  end
101
106
 
102
107
  # rubocop:disable Lint/NonLocalExitFromIterator
103
- def traverse_outer(act_part, all_parts, polylines, shapes, outer_joined_polyline)
108
+ def traverse_outer(act_part, all_parts, shapes_sequence, outer_joined_polyline)
104
109
  all_parts << act_part if all_parts.last != act_part
105
- shapes << act_part.polyline.shape if !shapes.include?(act_part.polyline.shape)
110
+
106
111
  if act_part.is?(Part::EXCLUSIVE)
107
112
  return if act_part.size == 0
108
113
  while (position = act_part.next_position)
@@ -125,9 +130,9 @@ module Contrek
125
130
  map = all_parts[-2..].map(&:type).uniq
126
131
  break if map.size == 1 && map.first == Part::SEAM
127
132
  end
128
- polylines << part.polyline.shape.outer_polyline
133
+ shapes_sequence.add(part.polyline.shape)
129
134
  part.next_position(new_position)
130
- traverse_outer(part, all_parts, polylines, shapes, outer_joined_polyline)
135
+ traverse_outer(part, all_parts, shapes_sequence, outer_joined_polyline)
131
136
  return
132
137
  end
133
138
  end
@@ -138,12 +143,13 @@ module Contrek
138
143
  end
139
144
  next_part = act_part.circular_next
140
145
  next_part.rewind!
141
- traverse_outer(act_part.circular_next, all_parts, polylines, shapes, outer_joined_polyline)
146
+ traverse_outer(act_part.circular_next, all_parts, shapes_sequence, outer_joined_polyline)
142
147
  end
143
148
 
144
149
  def collect_inner_sequences(outer_seq)
145
150
  return_sequences = []
146
- @polylines_sequence.each do |polyline|
151
+ @shapes_sequence.each do |shape|
152
+ polyline = shape.outer_polyline
147
153
  polyline.parts.each do |part|
148
154
  if part.innerable?
149
155
  all_parts = []
@@ -154,9 +160,9 @@ module Contrek
154
160
  retme_sequence = Sequence.new
155
161
  all_parts.each do |part|
156
162
  part.touch!
157
- retme_sequence.move_from(part) do |pos|
158
- next false if part.is?(Part::ADDED) && !(range_of_bounds === pos.payload[:y])
159
- !(polyline.tile.tg_border?(pos.payload) && pos.end_point.queues.include?(outer_seq))
163
+ retme_sequence.move_from(part) do |position|
164
+ next false if part.is?(Part::ADDED) && !(range_of_bounds === position.payload[:y])
165
+ !(polyline.tile.tg_border?(position.payload) && position.end_point.queues.include?(outer_seq))
160
166
  end
161
167
  end
162
168
  return_sequences << retme_sequence if retme_sequence.is_not_vertical
@@ -189,10 +195,12 @@ module Contrek
189
195
  if link_seq.any?
190
196
  ins_part = Part.new(Part::ADDED, act_part.polyline)
191
197
  link_seq.each do |pos|
192
- ins_part.add(Position.new(position: pos, hub: @cluster.hub))
198
+ ins_part.add(Position.new(position: nil, hub: nil, known_endpoint: pos))
193
199
  end
194
200
  all_parts << ins_part
195
201
  end
202
+ shape.outer_polyline.turn_on(Polyline::TRACKED_INNER)
203
+ shape.outer_polyline.turn_on(Polyline::TRACKED_OUTER)
196
204
  traverse_inner(dest_part.circular_next, all_parts, bounds)
197
205
  return
198
206
  end
@@ -213,9 +221,8 @@ module Contrek
213
221
  # a connection between parts inserted afterwards.
214
222
  # TODO evaluate the adoption of remove_adjacent_pairs!
215
223
  def duplicates_intersection(part_a, part_b)
216
- a1 = part_a.inverts ? part_a.remove_adjacent_pairs : part_a.to_a
217
- b1 = part_b.inverts ? part_b.remove_adjacent_pairs : part_b.to_a
218
-
224
+ a1 = part_a.inverts ? Part.remove_adjacent_pairs(part_a.to_endpoints) : part_a.to_endpoints
225
+ b1 = part_b.inverts ? Part.remove_adjacent_pairs(part_b.to_endpoints) : part_b.to_endpoints
219
226
  (a1 - b1) + (b1 - a1)
220
227
  end
221
228
 
@@ -1,9 +1,10 @@
1
1
  module Contrek
2
2
  module Concurrent
3
3
  class EndPoint
4
- attr_reader :queues
5
- def initialize
4
+ attr_reader :queues, :position
5
+ def initialize(position)
6
6
  @queues = []
7
+ @position = position
7
8
  end
8
9
 
9
10
  def inspect
@@ -73,6 +73,22 @@ module Contrek
73
73
  end
74
74
  false
75
75
  end
76
+
77
+ def to_endpoints
78
+ map(&:end_point)
79
+ end
80
+
81
+ def self.remove_adjacent_pairs(array = nil)
82
+ n = array.size
83
+ (0...(n - 1)).each do |i|
84
+ if array[i] == array[i + 1]
85
+ # Remove the pair and call recursively
86
+ new_array = array[0...i] + array[(i + 2)..]
87
+ return remove_adjacent_pairs(new_array)
88
+ end
89
+ end
90
+ array
91
+ end
76
92
  end
77
93
  end
78
94
  end
@@ -7,7 +7,7 @@ module Contrek
7
7
  TRACKED_INNER = 1 << 1
8
8
 
9
9
  attr_reader :raw, :name, :min_y, :max_y, :next_tile_eligible_shapes
10
- attr_accessor :shape, :tile
10
+ attr_accessor :shape, :tile, :mixed_tile_origin
11
11
 
12
12
  def initialize(tile:, polygon:, shape: nil, bounds: nil)
13
13
  @tile = tile
@@ -15,6 +15,7 @@ module Contrek
15
15
  @raw = polygon
16
16
  @shape = shape
17
17
  @flags = 0
18
+ @mixed_tile_origin = false # becomes true when is sewn with polyline coming from other side tile
18
19
 
19
20
  if bounds.nil?
20
21
  find_boundary
@@ -5,9 +5,14 @@ module Contrek
5
5
 
6
6
  attr_reader :end_point
7
7
 
8
- def initialize(hub:, position:)
9
- @end_point = hub.payloads[position[:y]] ||= EndPoint.new if hub
10
- @position = position
8
+ def initialize(hub:, position:, known_endpoint: nil)
9
+ if !known_endpoint.nil?
10
+ @end_point = known_endpoint
11
+ @position = @end_point.position
12
+ else
13
+ @end_point = hub.payloads[position[:y]] ||= EndPoint.new(position) if hub
14
+ @position = position
15
+ end
11
16
  end
12
17
 
13
18
  def payload
@@ -21,6 +26,10 @@ module Contrek
21
26
  def before_rem(old_queue)
22
27
  @end_point&.queues&.delete(old_queue)
23
28
  end
29
+
30
+ def inspect
31
+ "#{self.class} (#{payload})"
32
+ end
24
33
  end
25
34
  end
26
35
  end
@@ -153,19 +153,6 @@ module Contrek
153
153
  rem(@tail)
154
154
  end
155
155
 
156
- def remove_adjacent_pairs(array = nil)
157
- array = to_a if array.nil?
158
- n = array.size
159
- (0...(n - 1)).each do |i|
160
- if array[i] == array[i + 1]
161
- # Remove the pair and call recursively
162
- new_array = array[0...i] + array[(i + 2)..]
163
- return remove_adjacent_pairs(new_array)
164
- end
165
- end
166
- array
167
- end
168
-
169
156
  def remove_adjacent_pairs!
170
157
  unless @tail.nil?
171
158
  pointer = @tail
@@ -182,15 +169,6 @@ module Contrek
182
169
  end
183
170
  end
184
171
  end
185
-
186
- # def index(node)
187
- # index = 0
188
- # each do |loop_node|
189
- # return index if node == loop_node
190
- # index += 1
191
- # end
192
- # -1
193
- # end
194
172
  end
195
173
  end
196
174
  end
@@ -1,3 +1,3 @@
1
1
  module Contrek
2
- VERSION = "1.0.9"
2
+ VERSION = "1.1.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrek
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.9
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuele Cesaroni
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-31 00:00:00.000000000 Z
11
+ date: 2026-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec