contrek 1.1.2 → 1.1.3

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +6 -1
  4. data/Gemfile.lock +1 -1
  5. data/LICENSE-MIT.md +9 -0
  6. data/README.md +41 -1
  7. data/contrek.gemspec +0 -1
  8. data/ext/cpp_polygon_finder/PolygonFinder/CMakeLists.txt +14 -2
  9. data/ext/cpp_polygon_finder/PolygonFinder/LICENSE_AGPL.txt +661 -0
  10. data/ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp +9 -7
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +9 -0
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +2 -0
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +13 -2
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/List.cpp +74 -82
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/List.h +12 -4
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Lists.cpp +0 -10
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Lists.h +0 -7
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +39 -39
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +10 -10
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +24 -39
  21. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +5 -7
  22. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +6 -4
  23. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +4 -3
  24. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +9 -3
  25. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +1 -0
  26. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +33 -0
  27. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +5 -1
  28. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +8 -8
  29. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +2 -1
  30. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +13 -0
  31. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +2 -0
  32. data/ext/cpp_polygon_finder/extconf.rb +12 -2
  33. data/lib/contrek/bitmaps/painting.rb +1 -0
  34. data/lib/contrek/finder/concurrent/cursor.rb +5 -5
  35. data/lib/contrek/finder/concurrent/finder.rb +2 -1
  36. data/lib/contrek/finder/concurrent/part.rb +12 -3
  37. data/lib/contrek/finder/concurrent/partitionable.rb +7 -5
  38. data/lib/contrek/finder/node.rb +41 -29
  39. data/lib/contrek/finder/node_cluster.rb +18 -12
  40. data/lib/contrek/finder/polygon_finder.rb +4 -3
  41. data/lib/contrek/version.rb +1 -1
  42. metadata +8 -7
@@ -10,6 +10,7 @@
10
10
  #pragma once
11
11
  #include <list>
12
12
  #include <vector>
13
+ #include <deque>
13
14
  #include <map>
14
15
  #include <string>
15
16
  #include <utility>
@@ -32,24 +33,21 @@ class NodeCluster {
32
33
  int versus_inverter[2];
33
34
  int count = 0;
34
35
  int nodes;
35
- std::vector<Point> points;
36
+ int width;
36
37
 
37
38
  public:
38
39
  pf_Options *options;
39
40
  std::vector<std::pair<int, int>> treemap; // [a,b] a = index of parent outer, b = index of inner of parent outer
40
41
  std::pair<int, int> test_in_hole_a(Node* node);
41
42
  std::pair<int, int> test_in_hole_o(Node* node);
42
- std::vector<std::vector<Node>> vert_nodes;
43
- void list_track(Node *node, std::list<Node*> *list);
44
- void list_delete(Node *node, std::list<Node*> *list);
45
- bool list_present(Node *node, std::list<Node*> *list);
43
+ std::vector<std::deque<Node>> vert_nodes;
46
44
  void compress_coords(std::list<Polygon>& polygons, pf_Options options);
47
45
  List *root_nodes;
48
- int height, width;
46
+ int height;
49
47
  std::list<Polygon> polygons;
50
48
  NodeCluster(int h, int w, pf_Options *options);
51
49
  virtual ~NodeCluster();
52
- Node* add_node(int min_x, int max_x, int y, char name);
50
+ Node* add_node(int min_x, int max_x, int y, char name, int offset);
53
51
  void calc_root_nodes();
54
52
  void build_tangs_sequence();
55
53
  void plot(int versus);
@@ -37,6 +37,7 @@ PolygonFinder::PolygonFinder(Bitmap *bitmap,
37
37
  end_x(end_x == -1 ? bitmap->w() : end_x)
38
38
  { this->rgb_matcher = dynamic_cast<RGBMatcher*>(matcher);
39
39
  if (options != nullptr) FinderUtils::sanitize_options(this->options, options);
40
+
40
41
  this->node_cluster = new NodeCluster(source_bitmap->h(), source_bitmap->w(), &this->options);
41
42
 
42
43
  //= SCAN ==============//
@@ -82,17 +83,18 @@ PolygonFinder::~PolygonFinder() {
82
83
 
83
84
  void PolygonFinder::scan() {
84
85
  int bpp = this->source_bitmap->get_bytes_per_pixel();
86
+ int offset = options.connectivity_offset;
85
87
 
86
88
  RGBMatcher* rgb_m = dynamic_cast<RGBMatcher*>(this->matcher);
87
89
 
88
90
  if (bpp == 1) {
89
91
  auto fetcher = [](const unsigned char* p) { return static_cast<unsigned int>(p[0]); };
90
- if (rgb_m) run_loop(rgb_m, fetcher);
91
- else run_loop(this->matcher, fetcher);
92
+ if (rgb_m) run_loop(rgb_m, fetcher, offset);
93
+ else run_loop(this->matcher, fetcher, offset);
92
94
  } else {
93
95
  auto fetcher = [](const unsigned char* p) { return *reinterpret_cast<const uint32_t*>(p); };
94
- if (rgb_m) run_loop(rgb_m, fetcher);
95
- else run_loop(this->matcher, fetcher);
96
+ if (rgb_m) run_loop(rgb_m, fetcher, offset);
97
+ else run_loop(this->matcher, fetcher, offset);
96
98
  }
97
99
  }
98
100
 
@@ -41,6 +41,7 @@ struct pf_Options {
41
41
  bool compress_visvalingam = false;
42
42
  bool named_sequences = false;
43
43
  bool bounds = false;
44
+ int connectivity_offset = 0;
44
45
  float compress_visvalingam_tolerance = 10.0;
45
46
  int number_of_tiles = 1;
46
47
  std::string get_alpha_versus() {
@@ -94,7 +95,7 @@ class PolygonFinder {
94
95
  CpuTimer cpu_timer;
95
96
 
96
97
  template <typename M, typename F>
97
- void run_loop(M* specific_matcher, F&& fetch_color) {
98
+ void run_loop(M* specific_matcher, F&& fetch_color, int offset) {
98
99
  int img_h = this->source_bitmap->h();
99
100
  int bpp = this->source_bitmap->get_bytes_per_pixel();
100
101
  for (int y = 0; y < img_h; y++) {
@@ -114,11 +115,11 @@ class PolygonFinder {
114
115
  matching = true;
115
116
  }
116
117
  if (x == this->end_x - 1) {
117
- this->node_cluster->add_node(min_x, x, y, last_red_value);
118
+ this->node_cluster->add_node(min_x, x, y, last_red_value, offset);
118
119
  matching = false;
119
120
  }
120
121
  } else if (matching) {
121
- this->node_cluster->add_node(min_x, x - 1, y, last_red_value);
122
+ this->node_cluster->add_node(min_x, x - 1, y, last_red_value, offset);
122
123
  matching = false;
123
124
  }
124
125
  }
@@ -58,7 +58,8 @@ void Cursor::traverse_outer(Part* act_part,
58
58
  std::vector<Shape*>& shapes_sequence,
59
59
  Sequence* outer_joined_polyline) {
60
60
  while (act_part != nullptr) {
61
- if (all_parts.empty() || all_parts.back() != act_part) {
61
+ Part* last_part = (!all_parts.empty()) ? all_parts.back() : nullptr;
62
+ if (all_parts.empty() || last_part != act_part) {
62
63
  all_parts.push_back(act_part);
63
64
  }
64
65
 
@@ -75,6 +76,10 @@ void Cursor::traverse_outer(Part* act_part,
75
76
  outer_joined_polyline->add(position);
76
77
  }
77
78
  } else {
79
+ if (act_part->dead_end &&
80
+ all_parts.size() > 1 &&
81
+ last_part->is(Part::SEAM) &&
82
+ last_part->polyline() == act_part->polyline()) return;
78
83
  while (Position *new_position = static_cast<Position*>(act_part->iterator())) {
79
84
  if (outer_joined_polyline->size > 1 &&
80
85
  outer_joined_polyline->head->payload == new_position->payload &&
@@ -83,8 +88,9 @@ void Cursor::traverse_outer(Part* act_part,
83
88
  }
84
89
  this->cluster.positions_pool.emplace_back(this->cluster.hub(), new_position->payload);
85
90
  outer_joined_polyline->add(&this->cluster.positions_pool.back());
91
+
86
92
  for (Shape *shape : act_part->polyline()->next_tile_eligible_shapes()) {
87
- if (Part *part = shape->outer_polyline->find_first_part_by_position(new_position)) {
93
+ if (Part *part = shape->outer_polyline->find_first_part_by_position(new_position, act_part->versus())) {
88
94
  const auto n = all_parts.size();
89
95
  Part *last_last_part = n >= 2 ? all_parts[n - 2] : nullptr;
90
96
 
@@ -103,6 +109,7 @@ void Cursor::traverse_outer(Part* act_part,
103
109
  shapes_sequence.push_back(part->polyline()->shape);
104
110
  }
105
111
  part->next_position(new_position);
112
+ part->dead_end = true;
106
113
  act_part = part;
107
114
  jumped_to_new_part = true;
108
115
  break;
@@ -110,7 +117,6 @@ void Cursor::traverse_outer(Part* act_part,
110
117
  }
111
118
  }
112
119
  if (jumped_to_new_part) break;
113
- act_part->passes += 1;
114
120
  act_part->next_position(nullptr);
115
121
  }
116
122
  }
@@ -41,6 +41,7 @@ Finder::Finder(int number_of_threads, Bitmap *bitmap, Matcher *matcher, std::vec
41
41
  TilePayload p { tile_index, x, tile_end_x };
42
42
  enqueue(p, [this](const TilePayload& payload) {
43
43
  std::vector<std::string> base_arguments = {"--bounds", "--versus=" + this->options.get_alpha_versus()};
44
+ if (this->options.connectivity_offset == 1) base_arguments.push_back("--connectivity=8");
44
45
  CpuTimer t;
45
46
  t.start();
46
47
  auto* finder = new ClippedPolygonFinder(
@@ -10,6 +10,8 @@
10
10
  #include "Part.h"
11
11
  #include <iostream>
12
12
  #include <vector>
13
+ #include <string>
14
+ #include <sstream>
13
15
  #include "Polyline.h"
14
16
  #include "Tile.h"
15
17
  #include "Cluster.h"
@@ -94,3 +96,34 @@ std::vector<EndPoint*> Part::remove_adjacent_pairs(const std::vector<EndPoint*>&
94
96
  }
95
97
  return result;
96
98
  }
99
+
100
+ void Part::orient()
101
+ { if (this->size <= 1) {
102
+ this->versus_ = 0;
103
+ } else {
104
+ this->versus_ = (this->tail->payload->y - this->head->payload->y) > 0 ? 1 : -1;
105
+ }
106
+ }
107
+
108
+ std::string Part::inspect() {
109
+ size_t part_index = 0;
110
+ auto it = std::find(this->polyline()->parts().begin(), this->polyline()->parts().end(), this);
111
+ if (it != this->polyline()->parts().end()) {
112
+ part_index = std::distance(this->polyline()->parts().begin(), it);
113
+ }
114
+ std::stringstream ss;
115
+ ss << "part " << part_index
116
+ << " (versus=" << this->versus_
117
+ << " inv=" << this->inverts
118
+ << " trm=" << this->trasmuted
119
+ << " touched=" << this->touched_
120
+ << " dead_end=" << this->dead_end
121
+ << ", " << this->size << "x) of " << this->polyline()->tile->name()
122
+ << " (" << std::to_string(static_cast<uint32_t>(type)) << ") (";
123
+ this->each([&](QNode<Point>* pos) -> bool {
124
+ ss << pos->payload->toString();
125
+ return true;
126
+ });
127
+ ss << ")";
128
+ return ss.str();
129
+ }
@@ -32,6 +32,7 @@ class Part : public Queueable<Point> {
32
32
  bool inverts = false;
33
33
  bool trasmuted = false;
34
34
  bool delayed = false;
35
+ bool dead_end = false;
35
36
  Part* next = nullptr;
36
37
  Part* prev = nullptr;
37
38
  Part* circular_next = nullptr;
@@ -39,17 +40,20 @@ class Part : public Queueable<Point> {
39
40
  Polyline* polyline() { return polyline_; }
40
41
  Position* next_position(Position* force_position);
41
42
  void add_position(Point* point);
42
- int passes = 0;
43
43
  Types type;
44
44
  bool innerable();
45
45
  const bool touched() const { return touched_; }
46
+ const int versus() const { return versus_; }
46
47
  void touch();
47
48
  bool intersect_part(Part* other_part);
48
49
  void set_polyline(Polyline* polyline) { this->polyline_ = polyline; }
49
50
  std::vector<EndPoint*> to_endpoints();
50
51
  static std::vector<EndPoint*> remove_adjacent_pairs(const std::vector<EndPoint*>& input);
52
+ void orient();
53
+ std::string inspect();
51
54
 
52
55
  private:
56
+ int versus_ = 0;
53
57
  bool touched_ = false;
54
58
  Polyline* polyline_;
55
59
  };
@@ -31,6 +31,8 @@ void Partitionable::add_part(Part* new_part)
31
31
  }
32
32
  new_part->prev = last;
33
33
  new_part->circular_next = this->parts_.front();
34
+
35
+ if (new_part->is(Part::SEAM)) new_part->orient();
34
36
  }
35
37
 
36
38
  void Partitionable::insert_after(Part* part, Part* new_part) {
@@ -77,10 +79,10 @@ void Partitionable::partition()
77
79
  this->trasmute_parts();
78
80
  }
79
81
 
80
- Part* Partitionable::find_first_part_by_position(Position* position) {
82
+ Part* Partitionable::find_first_part_by_position(Position* position, int versus) {
81
83
  for (Part* part : this->parts_)
82
84
  { if ( part->is(Part::SEAM) &&
83
- part->passes == 0 &&
85
+ part->versus() == -(versus) &&
84
86
  position->end_point()->queues_include(part)) return(part);
85
87
  }
86
88
  return(nullptr);
@@ -194,7 +196,6 @@ std::optional<SewReturnData> Partitionable::sew(std::vector<std::pair<int, int>>
194
196
  return std::make_pair(left, right);
195
197
  }
196
198
 
197
-
198
199
  void Partitionable::trasmute_parts()
199
200
  { std::vector<Part*> insides;
200
201
  for (Part* p : parts_) {
@@ -205,16 +206,16 @@ void Partitionable::trasmute_parts()
205
206
  for (Part* inside : insides)
206
207
  { for (Part* inside_compare : insides) {
207
208
  if (inside == inside_compare || !inside_compare->is(Part::SEAM) ) continue;
208
- bool all_match = true;
209
+ int count = 0;
209
210
  inside->each([&](QNode<Point>* pos) -> bool {
210
211
  Position *position = dynamic_cast<Position*>(pos);
211
212
  if (position->end_point()->queues_include(inside_compare))
212
- { return true;
213
+ { count ++;
214
+ return true;
213
215
  }
214
- all_match = false;
215
216
  return false;
216
217
  });
217
- if (all_match) {
218
+ if (count == inside->size && count < inside_compare->size) {
218
219
  inside->type = Part::EXCLUSIVE;
219
220
  inside->trasmuted = true;
220
221
  break;
@@ -222,4 +223,3 @@ void Partitionable::trasmute_parts()
222
223
  }
223
224
  }
224
225
  }
225
-
@@ -20,8 +20,9 @@ class Partitionable {
20
20
  explicit Partitionable() {}
21
21
  virtual ~Partitionable() = default;
22
22
  void partition();
23
- Part* find_first_part_by_position(Position* position);
23
+ Part* find_first_part_by_position(Position* position, int versus);
24
24
  std::optional<SewReturnData> sew(std::vector<std::pair<int, int>> intersection, Polyline* other);
25
+ const std::vector<Part*> parts() const { return parts_; }
25
26
 
26
27
  protected:
27
28
  std::vector<Part*> parts_;
@@ -10,6 +10,7 @@
10
10
  #include <vector>
11
11
  #include <limits>
12
12
  #include <unordered_set>
13
+ #include <sstream>
13
14
  #include "Polyline.h"
14
15
  #include "Tile.h"
15
16
  #include "Shape.h"
@@ -114,3 +115,15 @@ void Polyline::clear() {
114
115
  bool Polyline::is_empty() {
115
116
  return raw_.empty();
116
117
  }
118
+
119
+ std::string Polyline::info() {
120
+ Shape* shape = this->shape;
121
+ size_t part_index = 0;
122
+ auto it = std::find(this->tile->shapes().begin(), this->tile->shapes().end(), shape);
123
+ if (it != this->tile->shapes().end()) {
124
+ part_index = std::distance(this->tile->shapes().begin(), it);
125
+ }
126
+ std::stringstream ss;
127
+ ss << "b" << this->tile->name() << " S" << part_index;
128
+ return ss.str();
129
+ }
@@ -14,6 +14,7 @@
14
14
  #include <utility>
15
15
  #include <vector>
16
16
  #include <unordered_map>
17
+ #include <string>
17
18
  #include "Partitionable.h"
18
19
  #include "../RectBounds.h"
19
20
 
@@ -47,6 +48,7 @@ class Polyline : public Partitionable {
47
48
  bool vert_intersect(Polyline& other);
48
49
  void reset_tracked_endpoints();
49
50
  bool mixed_tile_origin = false;
51
+ std::string info();
50
52
 
51
53
  private:
52
54
  std::vector<Point*> raw_;
@@ -1,9 +1,19 @@
1
1
  require "mkmf-rice"
2
2
 
3
+ has_tcmalloc = find_library("tcmalloc", "malloc")
4
+
3
5
  # rubocop:disable Style/GlobalVars
4
6
 
5
- $CXXFLAGS << " -std=c++17 -pthread -march=native -DNDEBUG -Ofast -flto -g -fno-omit-frame-pointer"
6
- $CFLAGS << " -std=c99 -pthread -march=native -DNDEBUG -Ofast -flto -g -fno-omit-frame-pointer"
7
+ $CXXFLAGS << " -std=c++17 -pthread -march=native -DNDEBUG -Ofast -flto"
8
+ $CFLAGS << " -std=c11 -pthread -march=native -DNDEBUG -Ofast -flto"
9
+
10
+ if has_tcmalloc
11
+ $LDFLAGS << " -Wl,--no-as-needed -ltcmalloc"
12
+ puts "tcmalloc linked to gem."
13
+ else
14
+ puts "tcmalloc not found; standard malloc will be used."
15
+ end
16
+
7
17
  $LDFLAGS << " -lz -lstdc++ -flto -pthread"
8
18
 
9
19
  $objs = [
@@ -28,6 +28,7 @@ module Contrek
28
28
  png_image.draw_line(poly[:outer][0][:x], poly[:outer][0][:y], poly[:outer][-1][:x], poly[:outer][-1][:y], color)
29
29
  color = ChunkyPNG::Color("green @ 1.0")
30
30
  poly[:inner].each do |sequence|
31
+ next if sequence.empty?
31
32
  sequence.each_cons(2) do |coords|
32
33
  png_image.draw_line(coords[0][:x], coords[0][:y], coords[1][:x], coords[1][:y], color)
33
34
  end
@@ -106,8 +106,8 @@ module Contrek
106
106
 
107
107
  # rubocop:disable Lint/NonLocalExitFromIterator
108
108
  def traverse_outer(act_part, all_parts, shapes_sequence, outer_joined_polyline)
109
- all_parts << act_part if all_parts.last != act_part
110
-
109
+ last_part = all_parts.last
110
+ all_parts << act_part if last_part != act_part
111
111
  if act_part.is?(Part::EXCLUSIVE)
112
112
  return if act_part.size == 0
113
113
  while (position = act_part.next_position)
@@ -117,14 +117,14 @@ module Contrek
117
117
  outer_joined_polyline.add(position)
118
118
  end
119
119
  else
120
+ return if act_part.dead_end && all_parts.size > 1 && last_part.is?(Part::SEAM) && last_part.polyline == act_part.polyline
120
121
  while (new_position = act_part.iterator)
121
122
  return if outer_joined_polyline.size > 1 &&
122
123
  outer_joined_polyline.head.payload == new_position.payload &&
123
124
  act_part == all_parts.first
124
125
  outer_joined_polyline.add(Position.new(position: new_position.payload, hub: @cluster.hub))
125
-
126
126
  act_part.polyline.next_tile_eligible_shapes.each do |shape|
127
- if (part = shape.outer_polyline.find_first_part_by_position(new_position))
127
+ if (part = shape.outer_polyline.find_first_part_by_position(new_position, act_part.versus))
128
128
  if all_parts[-2] != part
129
129
  if all_parts.size >= 2
130
130
  map = all_parts[-2..].map(&:type).uniq
@@ -132,12 +132,12 @@ module Contrek
132
132
  end
133
133
  shapes_sequence.add(part.polyline.shape)
134
134
  part.next_position(new_position)
135
+ part.dead_end = true
135
136
  traverse_outer(part, all_parts, shapes_sequence, outer_joined_polyline)
136
137
  return
137
138
  end
138
139
  end
139
140
  end
140
- act_part.passes += 1
141
141
  act_part.next_position
142
142
  end
143
143
  end
@@ -29,6 +29,7 @@ module Contrek
29
29
  @clusters = []
30
30
  @maximum_width = bitmap.w
31
31
  @number_of_tiles = options[:number_of_tiles] || (raise "number_of_tiles params is needed!")
32
+ @number_of_tiles = 1 if @number_of_tiles <= 0
32
33
 
33
34
  cw = @maximum_width.to_f / @number_of_tiles
34
35
  raise "One pixel tile width minimum!" if cw < 1.0
@@ -45,7 +46,7 @@ module Contrek
45
46
  finder = ClippedPolygonFinder.new(
46
47
  bitmap: bitmap,
47
48
  matcher: matcher,
48
- options: {versus: current_versus, bounds: true},
49
+ options: {versus: current_versus, bounds: true, connectivity: @options[:connectivity]}.compact,
49
50
  start_x: payload[:tile_start_x],
50
51
  end_x: payload[:tile_end_x]
51
52
  )
@@ -8,18 +8,19 @@ module Contrek
8
8
  ADDED = 2
9
9
 
10
10
  attr_reader :polyline, :index, :touched
11
- attr_accessor :next, :circular_next, :prev, :type, :passes, :inverts, :trasmuted, :delayed
11
+ attr_accessor :next, :circular_next, :prev, :type, :dead_end, :inverts, :trasmuted, :delayed, :versus
12
12
  def initialize(type, polyline)
13
13
  @type = type
14
14
  @polyline = polyline
15
15
  @next = nil
16
16
  @circular_next = nil
17
17
  @prev = nil
18
- @passes = 0
18
+ @dead_end = false
19
19
  @touched = false
20
20
  @inverts = false
21
21
  @trasmuted = false
22
22
  @delayed = false
23
+ @versus = 0
23
24
  end
24
25
 
25
26
  def is?(type)
@@ -60,7 +61,7 @@ module Contrek
60
61
  end
61
62
 
62
63
  def inspect
63
- "part #{polyline.parts.index(self)} (inv=#{@inverts} trm=#{@trasmuted} touched=#{@touched} passes=#{@passes}, #{size}x) of #{polyline.info} (#{name}) (#{to_a.map { |e| "[#{e[:x]},#{e[:y]}]" }.join})"
64
+ "part #{polyline.parts.index(self)} (versus=#{@versus} inv=#{@inverts} trm=#{@trasmuted} touched=#{@touched} dead_end =#{@dead_end}, #{size}x) of #{polyline.info} (#{name}) (#{to_a.map { |e| "[#{e[:x]},#{e[:y]}]" }.join})"
64
65
  end
65
66
 
66
67
  def innerable?
@@ -78,6 +79,14 @@ module Contrek
78
79
  map(&:end_point)
79
80
  end
80
81
 
82
+ def orient!
83
+ @versus = if size <= 1
84
+ 0
85
+ else
86
+ (tail.payload[:y] - head.payload[:y]).positive? ? 1 : -1
87
+ end
88
+ end
89
+
81
90
  def self.remove_adjacent_pairs(array = nil)
82
91
  n = array.size
83
92
  (0...(n - 1)).each do |i|
@@ -14,6 +14,8 @@ module Contrek
14
14
  last.next = last.circular_next = new_part if last
15
15
  new_part.circular_next = @parts.first
16
16
  new_part.prev = last
17
+
18
+ new_part.orient! if new_part.is?(Part::SEAM)
17
19
  end
18
20
 
19
21
  def insert_after(part, new_part)
@@ -25,10 +27,10 @@ module Contrek
25
27
  part.next = part.circular_next = new_part
26
28
  end
27
29
 
28
- def find_first_part_by_position(position)
30
+ def find_first_part_by_position(position, versus)
29
31
  @parts.find do |part|
30
32
  part.is?(Part::SEAM) &&
31
- part.passes == 0 &&
33
+ part.versus == -versus &&
32
34
  position.end_point.queues.include?(part)
33
35
  end
34
36
  end
@@ -144,10 +146,10 @@ module Contrek
144
146
 
145
147
  count = 0
146
148
  inside.each do |position|
147
- break unless position.end_point.queues.include?(inside_compare)
148
- count += 1
149
+ inclusion = position.end_point.queues.include?(inside_compare)
150
+ count += 1 if inclusion
149
151
  end
150
- if count == inside.size
152
+ if count == inside.size && count < inside_compare.size
151
153
  inside.type = Part::EXCLUSIVE
152
154
  inside.trasmuted = true
153
155
  break
@@ -3,7 +3,8 @@ module Contrek
3
3
  class Node
4
4
  include Listable
5
5
 
6
- attr_reader :min_x, :max_x, :y, :name, :tangs, :tangs_sequence, :tangs_count, :data_pointer
6
+ attr_reader :min_x, :max_x, :y, :name, :tangs_sequence, :tangs_count, :data_pointer,
7
+ :upper_start, :upper_end, :lower_start, :lower_end
7
8
  attr_accessor :track, :abs_x_index, :outer_index, :inner_index
8
9
 
9
10
  T_UP = -1
@@ -24,12 +25,11 @@ module Contrek
24
25
  OUTER = 0
25
26
  INNER = 1
26
27
 
27
- def initialize(cluster, min_x, max_x, y, name)
28
+ def initialize(cluster, min_x, max_x, y, name, connectivity_offset = 0)
28
29
  @name = name
29
30
  @min_x = min_x
30
31
  @max_x = max_x
31
32
  @y = y
32
- @tangs = {T_UP => [], T_DOWN => []}
33
33
  @tangs_sequence = nil
34
34
  @tangs_count = 0
35
35
  @track = 0
@@ -39,11 +39,12 @@ module Contrek
39
39
  @down_indexer = 0
40
40
  @outer_index = -1
41
41
  @inner_index = -1
42
- cluster.add_node(self)
43
- end
42
+ @upper_start = Float::INFINITY
43
+ @upper_end = -1
44
+ @lower_start = Float::INFINITY
45
+ @lower_end = -1
44
46
 
45
- def tangs?(node)
46
- @min_x <= node.max_x && node.min_x <= @max_x
47
+ cluster.add_node(self, connectivity_offset)
47
48
  end
48
49
 
49
50
  def my_next(last, versus, mode)
@@ -62,13 +63,11 @@ module Contrek
62
63
  end
63
64
 
64
65
  when :inner
65
-
66
66
  if versus == :o
67
67
  (last_node_index == 0) ? last_node_index = tangs_sequence.size - 1 : last_node_index -= 1
68
68
  else
69
69
  (last_node_index == tangs_sequence.size - 1) ? last_node_index = 0 : last_node_index += 1
70
70
  end
71
-
72
71
  end
73
72
  tangs_sequence.at(last_node_index)
74
73
  end
@@ -85,10 +84,6 @@ module Contrek
85
84
  coords_source[:point]
86
85
  end
87
86
 
88
- def tangs_with_x?(x)
89
- x.between?(@min_x, @max_x)
90
- end
91
-
92
87
  def track_uncomplete
93
88
  (@track & OCOMPLETE) != OCOMPLETE
94
89
  end
@@ -97,28 +92,45 @@ module Contrek
97
92
  (@track & OCOMPLETE) == OCOMPLETE
98
93
  end
99
94
 
100
- def add_intersection(other_node)
95
+ def add_intersection(other_node, other_node_index)
101
96
  if other_node.y < y
102
- @tangs[T_UP] << other_node
97
+ @upper_start = other_node_index if other_node_index < @upper_start
98
+ @upper_end = other_node_index if other_node_index > @upper_end
103
99
  else
104
- @tangs[T_DOWN] << other_node
100
+ @lower_start = other_node_index if other_node_index < @lower_start
101
+ @lower_end = other_node_index if other_node_index > @lower_end
105
102
  end
106
103
  end
107
104
 
108
- def precalc_tangs_sequences
109
- @tangs_sequence = Array.new(tangs[T_UP].size + tangs[T_DOWN].size)
110
- tangs = self.tangs[T_UP].sort_by(&:min_x)
111
- n = -1
112
- @up_indexer = -tangs[0].abs_x_index if tangs.size > 0
113
- tangs.each do |t_node|
114
- nd = Contrek::Finder::PolygonFinder::NodeDescriptor.new(t_node, {point: {x: t_node.max_x, y: t_node.y}, m: OMAX}, {point: {x: t_node.min_x, y: t_node.y}, m: OMIN})
115
- tangs_sequence[n += 1] = nd
105
+ def precalc_tangs_sequences(cluster:)
106
+ @tangs_sequence = []
107
+ @up_indexer = -cluster.vert_nodes[@y + T_UP][@upper_start].abs_x_index if @upper_end >= 0
108
+ if @upper_end >= 0
109
+ (@upper_start..@upper_end).each do |upper_pos|
110
+ t_node = cluster.vert_nodes[@y + T_UP][upper_pos]
111
+ @tangs_sequence << Contrek::Finder::PolygonFinder::NodeDescriptor.new(
112
+ t_node,
113
+ {point: {x: t_node.max_x, y: t_node.y}, m: OMAX},
114
+ {point: {x: t_node.min_x, y: t_node.y}, m: OMIN}
115
+ )
116
+ end
117
+ end
118
+
119
+ if @lower_end >= 0
120
+ lower_size = (@lower_end >= 0) ? (@lower_end - @lower_start + 1) : 0
121
+ upper_size = (@upper_end >= 0) ? (@upper_end - @upper_start + 1) : 0
122
+ @down_indexer = (cluster.vert_nodes[@y + T_DOWN][@lower_start].abs_x_index + lower_size + upper_size - 1)
116
123
  end
117
- tangs = self.tangs[T_DOWN].sort_by(&:min_x).reverse
118
- @down_indexer = (tangs.last.abs_x_index + self.tangs[T_DOWN].size + self.tangs[T_UP].size - 1) if tangs.size > 0
119
- tangs.each do |t_node|
120
- nd = Contrek::Finder::PolygonFinder::NodeDescriptor.new(t_node, {point: {x: t_node.min_x, y: t_node.y}, m: OMIN}, {point: {x: t_node.max_x, y: t_node.y}, m: OMAX})
121
- tangs_sequence[n += 1] = nd
124
+
125
+ if @lower_end >= 0
126
+ @lower_end.downto(@lower_start).each do |lower_pos|
127
+ t_node = cluster.vert_nodes[@y + T_DOWN][lower_pos]
128
+ @tangs_sequence << Contrek::Finder::PolygonFinder::NodeDescriptor.new(
129
+ t_node,
130
+ {point: {x: t_node.min_x, y: t_node.y}, m: OMIN},
131
+ {point: {x: t_node.max_x, y: t_node.y}, m: OMAX}
132
+ )
133
+ end
122
134
  end
123
135
  @tangs_count = tangs_sequence.size
124
136
  end