contrek 1.3.3 → 1.3.5

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/Gemfile.lock +3 -1
  4. data/README.md +28 -28
  5. data/contrek.gemspec +1 -0
  6. data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +2 -0
  7. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +0 -2
  8. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +11 -5
  9. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +8 -10
  10. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +4 -1
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +114 -42
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +1 -1
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +2 -1
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +1 -1
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +66 -116
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +1 -1
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +1 -0
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +2 -87
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +1 -13
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp +0 -1
  21. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +1 -57
  22. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +1 -4
  23. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +31 -17
  24. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.cpp +0 -16
  25. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.h +0 -9
  26. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +1 -1
  27. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.cpp +177 -0
  28. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.h +52 -0
  29. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.cpp +109 -0
  30. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.h +32 -0
  31. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.h +6 -1
  32. data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +4 -0
  33. data/lib/contrek/bitmaps/painting.rb +27 -10
  34. data/lib/contrek/bitmaps/png_bitmap.rb +3 -0
  35. data/lib/contrek/bitmaps/rendering.rb +75 -0
  36. data/lib/contrek/finder/concurrent/cursor.rb +62 -84
  37. data/lib/contrek/finder/concurrent/finder.rb +2 -1
  38. data/lib/contrek/finder/concurrent/part.rb +4 -77
  39. data/lib/contrek/finder/concurrent/partitionable.rb +1 -58
  40. data/lib/contrek/finder/concurrent/queueable.rb +23 -5
  41. data/lib/contrek/finder/concurrent/sequence.rb +0 -15
  42. data/lib/contrek/finder/concurrent/tile.rb +1 -1
  43. data/lib/contrek/finder/node.rb +26 -8
  44. data/lib/contrek/finder/node_cluster.rb +91 -37
  45. data/lib/contrek/finder/polygon_finder.rb +1 -1
  46. data/lib/contrek/reducers/douglas_peucker_reducer.rb +191 -0
  47. data/lib/contrek/reducers/raster_reducer.rb +72 -0
  48. data/lib/contrek/version.rb +1 -1
  49. data/lib/contrek.rb +3 -0
  50. metadata +23 -2
@@ -0,0 +1,177 @@
1
+ /*
2
+ * DouglasPeuckerReducer.cpp
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #include "DouglasPeuckerReducer.h"
11
+ #include <cstdint>
12
+ #include <limits>
13
+ #include <vector>
14
+
15
+ DouglasPeuckerReducer::DouglasPeuckerReducer(std::vector<Point>& list_of_points)
16
+ : Reducer(list_of_points) {
17
+ }
18
+
19
+ void DouglasPeuckerReducer::reduce() {
20
+ const std::size_t count = points.size();
21
+ if (count < 4) {
22
+ return;
23
+ }
24
+ const std::size_t first_index = extremeIndexByX();
25
+ const std::size_t second_index = farthestIndexFrom(first_index);
26
+ if (second_index == first_index) {
27
+ return;
28
+ }
29
+ std::vector<bool> keep(count, false);
30
+ keep[first_index] = true;
31
+ keep[second_index] = true;
32
+ simplifyArc(first_index, second_index, keep);
33
+ simplifyArc(second_index, first_index, keep);
34
+ compactRing(keep);
35
+ }
36
+
37
+ std::size_t DouglasPeuckerReducer::extremeIndexByX() const {
38
+ std::size_t best = 0;
39
+ int best_x = points[0].x;
40
+ for (std::size_t index = 1; index < points.size(); ++index) {
41
+ const int x = points[index].x;
42
+ if (x < best_x) {
43
+ best_x = x;
44
+ best = index;
45
+ }
46
+ }
47
+ return best;
48
+ }
49
+
50
+ std::size_t DouglasPeuckerReducer::farthestIndexFrom(std::size_t origin_index) const {
51
+ const Point& origin = points[origin_index];
52
+ std::size_t best_index = origin_index;
53
+ std::int64_t best_distance = -1;
54
+ for (std::size_t index = 0; index < points.size(); ++index) {
55
+ const std::int64_t dx = static_cast<std::int64_t>(points[index].x) - origin.x;
56
+ const std::int64_t dy = static_cast<std::int64_t>(points[index].y) - origin.y;
57
+ const std::int64_t distance = dx * dx + dy * dy;
58
+ if (distance > best_distance) {
59
+ best_distance = distance;
60
+ best_index = index;
61
+ }
62
+ }
63
+ return best_index;
64
+ }
65
+
66
+ void DouglasPeuckerReducer::simplifyArc(std::size_t first_index, std::size_t last_index, std::vector<bool>& keep) const {
67
+ const std::size_t count = points.size();
68
+ std::vector<Arc> stack;
69
+ stack.push_back({first_index, last_index});
70
+ while (!stack.empty()) {
71
+ const Arc arc = stack.back();
72
+ stack.pop_back();
73
+ std::size_t split_index = 0;
74
+ if (!farthestOnArc(
75
+ arc.first,
76
+ arc.last,
77
+ count,
78
+ split_index)) {
79
+ continue;
80
+ }
81
+ keep[split_index] = true;
82
+ if (nextIndex(arc.first, count) != split_index) {
83
+ stack.push_back({arc.first, split_index});
84
+ }
85
+ if (nextIndex(split_index, count) != arc.last) {
86
+ stack.push_back({split_index, arc.last});
87
+ }
88
+ }
89
+ }
90
+
91
+ bool DouglasPeuckerReducer::farthestOnArc(std::size_t first_index, std::size_t last_index,
92
+ std::size_t count, std::size_t& split_index) const {
93
+ const Point& first = points[first_index];
94
+ const Point& last = points[last_index];
95
+ const double ax = static_cast<double>(first.x);
96
+ const double ay = static_cast<double>(first.y);
97
+ const double bx = static_cast<double>(last.x);
98
+ const double by = static_cast<double>(last.y);
99
+ const double abx = bx - ax;
100
+ const double aby = by - ay;
101
+ const double length_squared = abx * abx + aby * aby;
102
+ double max_distance = TOLERANCE_SQUARED;
103
+ bool found = false;
104
+ std::size_t index = nextIndex(first_index, count);
105
+
106
+ while (index != last_index) {
107
+ const Point& point = points[index];
108
+ double distance;
109
+ if (length_squared == 0.0) {
110
+ const double dx = static_cast<double>(point.x) - ax;
111
+ const double dy = static_cast<double>(point.y) - ay;
112
+ distance = dx * dx + dy * dy;
113
+ } else {
114
+ distance = pointSegmentDistanceSquared(
115
+ static_cast<double>(point.x),
116
+ static_cast<double>(point.y),
117
+ ax,
118
+ ay,
119
+ bx,
120
+ by,
121
+ abx,
122
+ aby,
123
+ length_squared);
124
+ }
125
+ if (distance > max_distance) {
126
+ max_distance = distance;
127
+ split_index = index;
128
+ found = true;
129
+ }
130
+ index = nextIndex(index, count);
131
+ }
132
+ return found;
133
+ }
134
+
135
+ double DouglasPeuckerReducer::pointSegmentDistanceSquared(
136
+ double px,
137
+ double py,
138
+ double ax,
139
+ double ay,
140
+ double bx,
141
+ double by,
142
+ double abx,
143
+ double aby,
144
+ double length_squared) {
145
+ const double apx = px - ax;
146
+ const double apy = py - ay;
147
+ const double dot = apx * abx + apy * aby;
148
+ if (dot <= 0.0) {
149
+ return apx * apx + apy * apy;
150
+ }
151
+ if (dot >= length_squared) {
152
+ const double bpx = px - bx;
153
+ const double bpy = py - by;
154
+ return bpx * bpx + bpy * bpy;
155
+ }
156
+ const double cross = apx * aby - apy * abx;
157
+ return (cross * cross) / length_squared;
158
+ }
159
+
160
+ std::size_t DouglasPeuckerReducer::nextIndex(std::size_t index, std::size_t count) {
161
+ return index + 1 == count ? 0 : index + 1;
162
+ }
163
+
164
+ void DouglasPeuckerReducer::compactRing(const std::vector<bool>& keep) {
165
+ std::size_t write_index = 0;
166
+ for (
167
+ std::size_t read_index = 0;
168
+ read_index < points.size();
169
+ ++read_index
170
+ ) {
171
+ if (keep[read_index]) {
172
+ points[write_index] = points[read_index];
173
+ ++write_index;
174
+ }
175
+ }
176
+ points.resize(write_index);
177
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ * DouglasPeuckerReducer.h
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #pragma once
11
+ #include <cstddef>
12
+ #include <vector>
13
+ #include "Reducer.h"
14
+
15
+ struct Point;
16
+
17
+ class DouglasPeuckerReducer : public Reducer {
18
+ public:
19
+ explicit DouglasPeuckerReducer(std::vector<Point>& list_of_points);
20
+ void reduce() override;
21
+
22
+ private:
23
+ static constexpr double TOLERANCE_SQUARED = 1.0;
24
+ struct Arc {
25
+ std::size_t first;
26
+ std::size_t last;
27
+ };
28
+ std::size_t extremeIndexByX() const;
29
+ std::size_t farthestIndexFrom(
30
+ std::size_t origin_index) const;
31
+ void simplifyArc(
32
+ std::size_t first_index,
33
+ std::size_t last_index,
34
+ std::vector<bool>& keep) const;
35
+ bool farthestOnArc(
36
+ std::size_t first_index,
37
+ std::size_t last_index,
38
+ std::size_t count,
39
+ std::size_t& split_index) const;
40
+ static double pointSegmentDistanceSquared(
41
+ double px,
42
+ double py,
43
+ double ax,
44
+ double ay,
45
+ double bx,
46
+ double by,
47
+ double abx,
48
+ double aby,
49
+ double length_squared);
50
+ static std::size_t nextIndex(std::size_t index, std::size_t count);
51
+ void compactRing(const std::vector<bool>& keep);
52
+ };
@@ -0,0 +1,109 @@
1
+ /*
2
+ * RasterReducer.cpp
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #include "RasterReducer.h"
11
+ #include <stdexcept>
12
+ #include <string>
13
+
14
+ RasterReducer::RasterReducer(std::vector<Point>& points, int versus)
15
+ : Reducer(points),
16
+ versus_(versus) {
17
+ }
18
+
19
+ void RasterReducer::reduce() {
20
+ toRasterPolygon(points, versus_);
21
+ }
22
+
23
+ void RasterReducer::toRasterPolygon(std::vector<Point>& points, int versus) {
24
+ const std::size_t n = points.size();
25
+
26
+ if (n == 0) {
27
+ return;
28
+ }
29
+
30
+ const bool outer = versus == Node::O;
31
+ const int firstX = points.front().x;
32
+ const int firstY = points.front().y;
33
+ int previousX = points.back().x;
34
+ int previousY = points.back().y;
35
+
36
+ for (std::size_t i = 0; i < n; ++i) {
37
+ const int currentX = points[i].x;
38
+ const int currentY = points[i].y;
39
+ int nextX;
40
+ int nextY;
41
+ if (i + 1 < n) {
42
+ nextX = points[i + 1].x;
43
+ nextY = points[i + 1].y;
44
+ } else {
45
+ nextX = firstX;
46
+ nextY = firstY;
47
+ }
48
+ const Pole incomingSide = segmentPole(
49
+ currentX - previousX,
50
+ currentY - previousY,
51
+ outer);
52
+ const Pole outgoingSide = segmentPole(
53
+ nextX - currentX,
54
+ nextY - currentY,
55
+ outer);
56
+ int x = currentX;
57
+ int y = currentY;
58
+ const auto applySide = [&x, &y](Pole side) {
59
+ switch (side) {
60
+ case Pole::South:
61
+ --y;
62
+ break;
63
+
64
+ case Pole::East:
65
+ --x;
66
+ break;
67
+
68
+ case Pole::North:
69
+ case Pole::West:
70
+ break;
71
+ }
72
+ };
73
+ applySide(incomingSide);
74
+ if (outgoingSide != incomingSide) {
75
+ applySide(outgoingSide);
76
+ }
77
+ points[i].x = x;
78
+ points[i].y = y;
79
+ previousX = currentX;
80
+ previousY = currentY;
81
+ }
82
+ }
83
+
84
+ RasterReducer::Pole RasterReducer::segmentPole(int dx, int dy, bool clockwise) {
85
+ const int sx = sign(dx);
86
+ const int sy = sign(dy);
87
+ if (sx == 0 && sy == 1) {
88
+ return clockwise ? Pole::East : Pole::West;
89
+ }
90
+ if (sx == 1 && sy == 0) {
91
+ return clockwise ? Pole::North : Pole::South;
92
+ }
93
+ if (sx == 0 && sy == -1) {
94
+ return clockwise ? Pole::West : Pole::East;
95
+ }
96
+ if (sx == -1 && sy == 0) {
97
+ return clockwise ? Pole::South : Pole::North;
98
+ }
99
+ throw std::invalid_argument(
100
+ "Non-axial segment dx=" +
101
+ std::to_string(dx) +
102
+ " dy=" +
103
+ std::to_string(dy) +
104
+ "!");
105
+ }
106
+
107
+ int RasterReducer::sign(int value) {
108
+ return (value > 0) - (value < 0);
109
+ }
@@ -0,0 +1,32 @@
1
+ /*
2
+ * RasterReducer.h
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #pragma once
11
+ #include <vector>
12
+ #include "Reducer.h"
13
+
14
+ struct Point;
15
+
16
+ class RasterReducer : public Reducer {
17
+ public:
18
+ RasterReducer(std::vector<Point>& points, int versus);
19
+ void reduce() override;
20
+
21
+ private:
22
+ enum class Pole {
23
+ North,
24
+ South,
25
+ East,
26
+ West
27
+ };
28
+ int versus_;
29
+ void toRasterPolygon(std::vector<Point>& points, int versus);
30
+ static Pole segmentPole(int dx, int dy, bool clockwise);
31
+ static int sign(int value);
32
+ };
@@ -25,7 +25,12 @@ class VisvalingamReducer : public Reducer {
25
25
  class Triangle {
26
26
  public:
27
27
  static int area(const Point& a, const Point& b, const Point& c) {
28
- return std::abs(((c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y)) / 2);
28
+ const int cross = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
29
+ int half = cross / 2;
30
+ if (cross < 0 && cross % 2 != 0) {
31
+ --half;
32
+ }
33
+ return std::abs(half);
29
34
  }
30
35
  };
31
36
 
@@ -52,6 +52,10 @@
52
52
  #include "PolygonFinder/src/polygon/reducers/LinearReducer.h"
53
53
  #include "PolygonFinder/src/polygon/reducers/VisvalingamReducer.cpp"
54
54
  #include "PolygonFinder/src/polygon/reducers/VisvalingamReducer.h"
55
+ #include "PolygonFinder/src/polygon/reducers/RasterReducer.cpp"
56
+ #include "PolygonFinder/src/polygon/reducers/RasterReducer.h"
57
+ #include "PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.cpp"
58
+ #include "PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.h"
55
59
  #include "PolygonFinder/src/polygon/finder/concurrent/Finder.h"
56
60
  #include "PolygonFinder/src/polygon/finder/concurrent/Finder.cpp"
57
61
  #include "PolygonFinder/src/polygon/finder/concurrent/Poolable.h"
@@ -21,23 +21,40 @@ module Contrek
21
21
  colors.sort_by { |color, count| -color }
22
22
  end
23
23
 
24
- def self.direct_draw_polygons(polygons, png_image)
24
+ def self.direct_draw_polygons(polygons, png_image, zoom: 1)
25
25
  polygons.compact.each do |poly|
26
- color = ChunkyPNG::Color("red @ 1.0")
27
- poly[:outer].each_cons(2) do |coords|
28
- png_image.draw_line(coords[0][:x], coords[0][:y], coords[1][:x], coords[1][:y], color)
26
+ if (bounds = poly[:bounds])
27
+ b_factor = 3.to_f / zoom
28
+ bounds_polygon = [
29
+ {x: bounds[:min_x] - b_factor, y: bounds[:min_y] - b_factor},
30
+ {x: bounds[:max_x] + b_factor, y: bounds[:min_y] - b_factor},
31
+ {x: (bounds[:max_x] + b_factor), y: bounds[:max_y] + b_factor},
32
+ {x: (bounds[:min_x] - b_factor), y: bounds[:max_y] + b_factor}
33
+ ]
34
+ draw_polygon(bounds_polygon, png_image, ChunkyPNG::Color("blue @ 1.0"), zoom)
29
35
  end
30
- png_image.draw_line(poly[:outer][0][:x], poly[:outer][0][:y], poly[:outer][-1][:x], poly[:outer][-1][:y], color)
36
+ color = ChunkyPNG::Color("red @ 1.0")
37
+ draw_polygon(poly[:outer], png_image, color, zoom)
31
38
  color = ChunkyPNG::Color("green @ 1.0")
32
39
  poly[:inner].each do |sequence|
33
- next if sequence.empty?
34
- sequence.each_cons(2) do |coords|
35
- png_image.draw_line(coords[0][:x], coords[0][:y], coords[1][:x], coords[1][:y], color)
36
- end
37
- png_image.draw_line(sequence[0][:x], sequence[0][:y], sequence[-1][:x], sequence[-1][:y], color)
40
+ draw_polygon(sequence, png_image, color, zoom)
38
41
  end
39
42
  end
40
43
  end
44
+
45
+ def self.draw_polygon(sequence, png_image, color, zoom, margin = 0)
46
+ return if sequence.empty?
47
+ sequence.each_cons(2) do |coords|
48
+ png_image.draw_line(coords[0][:x] * zoom,
49
+ coords[0][:y] * zoom,
50
+ coords[1][:x] * zoom,
51
+ coords[1][:y] * zoom, color)
52
+ end
53
+ png_image.draw_line(sequence[0][:x] * zoom,
54
+ sequence[0][:y] * zoom,
55
+ sequence[-1][:x] * zoom,
56
+ sequence[-1][:y] * zoom, color)
57
+ end
41
58
  end
42
59
  end
43
60
  end
@@ -5,6 +5,9 @@ require "chunky_png"
5
5
  module Contrek
6
6
  module Bitmaps
7
7
  class PngBitmap < Bitmap
8
+ include Rendering
9
+
10
+ attr_reader :image
8
11
  def initialize(file_path)
9
12
  @image = ChunkyPNG::Image.from_file(file_path)
10
13
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Contrek
4
+ module Bitmaps
5
+ module Rendering
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def to_window(result, zoom: 100, coords: true, window_w: 2000, window_h: 1200, font_h: 20)
12
+ require "ruby2d"
13
+ ubuntu_purple = ChunkyPNG::Color.from_hex("#77216F")
14
+ bm = RawBitmap.new(w: window_w, h: window_h, color: ubuntu_purple)
15
+ # grid
16
+ light_purple = ChunkyPNG::Color.from_hex("#95338B")
17
+ (window_w / zoom).times { |x| bm.draw_line(x * zoom, 0, x * zoom, window_h, light_purple) }
18
+ (window_h / zoom).times { |y| bm.draw_line(0, y * zoom, window_w, y * zoom, light_purple) }
19
+ # polygons
20
+ Painting.direct_draw_polygons(result.points, bm, zoom: zoom)
21
+ # windowing
22
+ Tempfile.create do |temp_file|
23
+ bm.image.save(temp_file.path, color_mode: ChunkyPNG::COLOR_TRUECOLOR_ALPHA)
24
+ Window.clear
25
+ Window.set(
26
+ title: "Rendering #{result[:metadata][:groups]} polygons",
27
+ width: window_w,
28
+ height: window_h,
29
+ resizable: true
30
+ )
31
+ Image.new(
32
+ temp_file.path,
33
+ x: 0, y: 0,
34
+ width: window_w,
35
+ height: window_h
36
+ )
37
+ draw_labels(result.points, zoom: zoom, draw_coords: coords, font_h:)
38
+ Window.on :key_down do |event|
39
+ if event.key == "escape"
40
+ Window.close
41
+ end
42
+ if event.key == "s"
43
+ Window.screenshot("window_content.png")
44
+ end
45
+ end
46
+ Window.show
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def draw_labels(polygons, font_h:, zoom: 1.0, draw_coords: true)
53
+ positions = []
54
+ polygons.compact.each do |poly|
55
+ ([poly[:outer]] + poly[:inner]).each do |seq|
56
+ seq.each_with_index do |coords, n|
57
+ x = coords[:x] * zoom
58
+ y = coords[:y] * zoom
59
+ y += font_h + 2 if positions.index([x, y])
60
+ t = (n + 1).to_s
61
+ t += " - [#{coords[:x]},#{coords[:y]}]" if draw_coords
62
+ draw_text(x, y, t, font_h) unless draw_coords.nil?
63
+ positions << [x, y]
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def draw_text(x, y, text, font_h)
70
+ Text.new(text, x:, y:, size: font_h, color: "white", z: 10)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end