contrek 1.1.9 → 1.2.1
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.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +30 -9
- data/Rakefile +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/CMakeLists.txt +10 -12
- data/ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp +6 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +36 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +4 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +2 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +66 -158
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +0 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/EndPoint.h +4 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.h +1 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +0 -13
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +1 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +0 -128
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +0 -6
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +0 -50
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +2 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.cpp +22 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +1 -57
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +0 -12
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +0 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp +11 -3
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +10 -0
- data/lib/contrek/finder/concurrent/cluster.rb +2 -3
- data/lib/contrek/finder/concurrent/cursor.rb +56 -120
- data/lib/contrek/finder/concurrent/end_point.rb +2 -0
- data/lib/contrek/finder/concurrent/hub.rb +1 -1
- data/lib/contrek/finder/concurrent/part.rb +2 -20
- data/lib/contrek/finder/concurrent/partitionable.rb +0 -81
- data/lib/contrek/finder/concurrent/polyline.rb +3 -47
- data/lib/contrek/finder/concurrent/position.rb +8 -2
- data/lib/contrek/finder/concurrent/queueable.rb +0 -41
- data/lib/contrek/finder/concurrent/tile.rb +0 -4
- data/lib/contrek/finder/concurrent/vertical_merger.rb +9 -5
- data/lib/contrek/version.rb +1 -1
- metadata +3 -3
- /data/{LICENSE-MIT.md → lib/LICENSE-MIT.md} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e7576fef74116c62a018f66a13d9e29d215f7b505637b40bcb16631250fed0d
|
|
4
|
+
data.tar.gz: 7ba2f39e97a44eaddd13361b4dc5a8f8cb1790a93dab327c1794d61e0ffc45a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 748e4750b607bc2a9e9e916fc0a00a96db530e94658822d1d6eb97508fa9e302e5def958a63e621a2f97be1396bb0acad4971aea936cbca700c1ea93ac5acdaf
|
|
7
|
+
data.tar.gz: 804a329359ea6648e4bcbf4babdb8af53a12205851d669b562fa37d5aa69a49f8b6988a143f2783c85b2681432f65ff52b82db8f207718bd29b16c46882f518d
|
data/CHANGELOG.md
CHANGED
|
@@ -80,3 +80,7 @@ All notable changes to this project will be documented in this file.
|
|
|
80
80
|
## [1.1.9] - 2026-04-24
|
|
81
81
|
### Changed
|
|
82
82
|
- Improved the internal parts joining algorithm, which was imprecise in some circumstances.
|
|
83
|
+
|
|
84
|
+
## [1.2.0] - 2026-05-02
|
|
85
|
+
### Changed
|
|
86
|
+
- Further improvements have been applied to the internal parts joining algorithm using a new structural approach. This update is faster and resolves edge cases where inner parts were mistakenly classified as outer perimeters, ensuring precise contour hierarchy. The simplified logic has led to a significant reduction in codebase complexity and the removal of substantial redundant code.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -49,6 +49,17 @@ The core strength of Contrek is its **Topologically Consistent Merging** algorit
|
|
|
49
49
|
</tr>
|
|
50
50
|
</table>
|
|
51
51
|
|
|
52
|
+
## 📊 Benchmarking & Performance
|
|
53
|
+
The **Stripe-Merging** algorithm has been validated through a dedicated testing suite comparing **Contrek** against **OpenCV** (industry-standard contour extraction).
|
|
54
|
+
|
|
55
|
+
### Key Metrics:
|
|
56
|
+
* **Execution Latency:** Single-threaded OpenCV vs. Contrek's parallel thread management.
|
|
57
|
+
* **Memory Footprint:** RAM consumption during ultra-high-resolution processing.
|
|
58
|
+
* **Extraction Fidelity:** Verifying polygon precision across both engines.
|
|
59
|
+
|
|
60
|
+
The complete testing suite, source code, and raw benchmarks are available here:
|
|
61
|
+
👉 **[test_opencv_contrek](https://github.com/runout77/test_opencv_contrek)**
|
|
62
|
+
|
|
52
63
|
## Prerequisites
|
|
53
64
|
|
|
54
65
|
For optimal performance and efficient memory management with large images (20k+), it is highly recommended to install **tcmalloc**.
|
|
@@ -57,7 +68,11 @@ For optimal performance and efficient memory management with large images (20k+)
|
|
|
57
68
|
```bash
|
|
58
69
|
sudo apt-get install libgoogle-perftools-dev
|
|
59
70
|
```
|
|
71
|
+
> For advanced performance tuning (zlib-ng, tcmalloc, thread configuration) see [PERFORMANCE.md](PERFORMANCE.md).
|
|
60
72
|
|
|
73
|
+
> ⚠️ **Platform support:** Contrek native extensions are supported on **Linux** and **macOS** only.
|
|
74
|
+
> Windows is not supported due to the use of POSIX threading primitives and platform-specific
|
|
75
|
+
> memory management. On Windows, consider using WSL2 (Windows Subsystem for Linux).
|
|
61
76
|
|
|
62
77
|
## Install
|
|
63
78
|
|
|
@@ -167,7 +182,7 @@ Regarding multithreading:
|
|
|
167
182
|
|
|
168
183
|
- The algorithm splits the contour-detection workflow into multiple phases that can be executed in parallel. The initial contour extraction on each band and the subsequent merging of coordinates between adjacent bands—performed pairwise, recursively, and in a non-deterministic order—results in a final output that is not idempotent. Idempotence is guaranteed only when the exact same merging sequence is repeated.
|
|
169
184
|
|
|
170
|
-
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
|
|
185
|
+
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 0.816 secs with image load (0.37 secs)).
|
|
171
186
|
|
|
172
187
|
```ruby
|
|
173
188
|
result = Contrek.contour!(
|
|
@@ -181,11 +196,11 @@ result = Contrek.contour!(
|
|
|
181
196
|
)
|
|
182
197
|
puts result.metadata[:benchmarks].inspect
|
|
183
198
|
|
|
184
|
-
{ compress:
|
|
185
|
-
init:
|
|
186
|
-
inner:
|
|
187
|
-
outer:
|
|
188
|
-
total:
|
|
199
|
+
{ compress: 23.83,
|
|
200
|
+
init: 313.179,
|
|
201
|
+
inner: 6.16,
|
|
202
|
+
outer: 78.791,
|
|
203
|
+
total: 337.013
|
|
189
204
|
}
|
|
190
205
|
|
|
191
206
|
```
|
|
@@ -232,9 +247,10 @@ Engineered for **Pixel-Perfect** precision.
|
|
|
232
247
|
* **Result:** 100% topologically faithful geometry with no micro-gaps between adjacent polygons.
|
|
233
248
|
|
|
234
249
|
Below are two images illustrating the difference in tracing modes. In the first case, with **strict_bounds ON**, the anti-clockwise sequence includes two additional points, **H** and **I**, which trace the shape more accurately. In the second case, the transition between **G** and **H** is approximated, omitting the indentation.
|
|
250
|
+
|
|
235
251
|
| Strict Bounds ON | Strict Bounds OFF |
|
|
236
252
|
|:---:|:---:|
|
|
237
|
-
|
|
|
253
|
+
| <img src="./docs/images/strict_bounds_on.png" alt="Originale" width="60%"/> | <img src="./docs/images/strict_bounds_off.png" alt="Poligoni" width="60%"/> |
|
|
238
254
|
|
|
239
255
|
|
|
240
256
|
## Result
|
|
@@ -411,6 +427,8 @@ This the one for the native C++
|
|
|
411
427
|
|
|
412
428
|
About 130x faster. Times are in microseconds; system: AMD Ryzen 7 3700X 8-Core Processor (BogoMIPS: 7199,99) on Ubuntu distro.
|
|
413
429
|
|
|
430
|
+
**Note:** Benchmarks were measured inside a VMware virtual machine.
|
|
431
|
+
|
|
414
432
|
## 🛠 C++ Standalone Library Usage
|
|
415
433
|
|
|
416
434
|
The core of **Contrek** is a high-performance `C++17` library. It is designed to be **standalone**, meaning it has zero dependencies on Ruby and can be integrated into any `C++` project.
|
|
@@ -483,9 +501,12 @@ int main() {
|
|
|
483
501
|
|
|
484
502
|
## License
|
|
485
503
|
|
|
486
|
-
|
|
504
|
+
Contrek uses a dual-license model:
|
|
505
|
+
|
|
506
|
+
- **Ruby gem and wrappers** — [MIT](lib/LICENSE-MIT.md). Free to use in any project, including commercial ones.
|
|
507
|
+
- **C++17 core engine** (`ext/cpp_polygon_finder/PolygonFinder`) — [AGPLv3](ext/cpp_polygon_finder/PolygonFinder/LICENSE_AGPL.txt). If you use the core in a SaaS or closed-source product, you must either open your source or [contact the author](https://github.com/runout77) for a commercial license.
|
|
487
508
|
|
|
488
|
-
See [LICENSE.md](LICENSE.md).
|
|
509
|
+
See [LICENSE.md](LICENSE.md) for full details.
|
|
489
510
|
|
|
490
511
|
## Changelog
|
|
491
512
|
|
data/Rakefile
CHANGED
|
@@ -6,7 +6,7 @@ task :compile do |t|
|
|
|
6
6
|
Dir.glob("**/*.o").each { |f| File.delete(f) }
|
|
7
7
|
File.delete("Makefile") if File.exist?("Makefile")
|
|
8
8
|
system "ruby", "extconf.rb"
|
|
9
|
-
system "make", "-B"
|
|
9
|
+
system "make", "-j#{`nproc`.strip}", "-B"
|
|
10
10
|
Dir.glob("**/*.o").each { |f| File.delete(f) }
|
|
11
11
|
system "cp cpp_polygon_finder.so ./../../lib"
|
|
12
12
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
cmake_minimum_required(VERSION 3.10)
|
|
2
2
|
project(ContrekCore C CXX)
|
|
3
|
-
|
|
4
3
|
set(CMAKE_CXX_STANDARD 17)
|
|
5
4
|
set(CMAKE_C_STANDARD 11)
|
|
6
5
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|
@@ -22,28 +21,27 @@ else()
|
|
|
22
21
|
message(WARNING "Contrek: tcmalloc not found; standard one will be used.")
|
|
23
22
|
endif()
|
|
24
23
|
endif()
|
|
25
|
-
|
|
26
24
|
find_package(ZLIB REQUIRED)
|
|
27
|
-
|
|
25
|
+
message(STATUS "Contrek: ZLIB path found at ${ZLIB_LIBRARIES}")
|
|
28
26
|
file(GLOB_RECURSE CPP_SOURCES "*.cpp")
|
|
29
27
|
file(GLOB_RECURSE C_SOURCES "*.c")
|
|
30
|
-
|
|
31
28
|
list(FILTER CPP_SOURCES EXCLUDE REGEX "examples/.*\\.cpp")
|
|
32
|
-
|
|
33
29
|
add_library(ContrekLib STATIC ${CPP_SOURCES} ${C_SOURCES})
|
|
34
|
-
|
|
35
30
|
file(GLOB_RECURSE ALL_HEADERS "*.h")
|
|
36
31
|
foreach(header_file ${ALL_HEADERS})
|
|
37
32
|
get_filename_component(header_dir ${header_file} DIRECTORY)
|
|
38
33
|
list(APPEND ALL_INCLUDE_DIRS ${header_dir})
|
|
39
34
|
endforeach()
|
|
40
35
|
list(REMOVE_DUPLICATES ALL_INCLUDE_DIRS)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
target_include_directories(ContrekLib PUBLIC
|
|
37
|
+
${ALL_INCLUDE_DIRS}
|
|
38
|
+
${ZLIB_INCLUDE_DIRS}
|
|
39
|
+
)
|
|
40
|
+
target_link_libraries(ContrekLib PRIVATE
|
|
41
|
+
${ZLIB_LIBRARIES}
|
|
42
|
+
pthread
|
|
43
|
+
)
|
|
45
44
|
option(BUILD_EXAMPLES "Build the example application" OFF)
|
|
46
|
-
|
|
47
45
|
if(BUILD_EXAMPLES)
|
|
48
46
|
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples/example.cpp")
|
|
49
47
|
message(STATUS "Contrek: Compiling example option ON")
|
|
@@ -56,4 +54,4 @@ if(BUILD_EXAMPLES)
|
|
|
56
54
|
else()
|
|
57
55
|
message(WARNING "Contrek: examples/example.cpp not found!")
|
|
58
56
|
endif()
|
|
59
|
-
endif()
|
|
57
|
+
endif()
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
#include <iostream>
|
|
11
|
+
#include <gperftools/malloc_extension.h>
|
|
11
12
|
#include "ContrekApi.h"
|
|
12
13
|
#include "Tests.h"
|
|
13
14
|
|
|
@@ -29,6 +30,11 @@ void run_test() {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
int main() {
|
|
33
|
+
MallocExtension::instance()->SetNumericProperty(
|
|
34
|
+
"tcmalloc.max_total_thread_cache_bytes",
|
|
35
|
+
1024 * 1024 * 1024
|
|
36
|
+
);
|
|
37
|
+
|
|
32
38
|
Contrek::Config cfg;
|
|
33
39
|
cfg.threads = 4;
|
|
34
40
|
cfg.tiles = 4;
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
#include "Node.h"
|
|
18
18
|
#include "NodeCluster.h"
|
|
19
19
|
|
|
20
|
+
static const int TURNER[2][2] = {{Node::OMAX, Node::OMIN}, {Node::TURN_MAX, Node::TURN_MIN}};
|
|
21
|
+
|
|
20
22
|
Node::Node(int min_x, int max_x, int y, NodeCluster* cluster, char name)
|
|
21
23
|
: start_point(min_x, y),
|
|
22
24
|
end_point(max_x, y),
|
|
@@ -14,8 +14,43 @@
|
|
|
14
14
|
#include <limits>
|
|
15
15
|
#include <algorithm>
|
|
16
16
|
#include <map>
|
|
17
|
+
#include <cstring>
|
|
18
|
+
#include <cstddef>
|
|
17
19
|
#include "List.h"
|
|
18
20
|
|
|
21
|
+
struct SmallVec {
|
|
22
|
+
static constexpr int INLINE_CAP = 6;
|
|
23
|
+
int buf[INLINE_CAP];
|
|
24
|
+
int* ptr = buf;
|
|
25
|
+
int sz = 0, cap = INLINE_CAP;
|
|
26
|
+
int front() const { return ptr[0]; }
|
|
27
|
+
int back() const { return ptr[sz - 1]; }
|
|
28
|
+
void push_back(int v) {
|
|
29
|
+
if (sz == cap) {
|
|
30
|
+
cap *= 2;
|
|
31
|
+
int* np = new int[cap];
|
|
32
|
+
std::memcpy(np, ptr, sz * sizeof(int));
|
|
33
|
+
if (ptr != buf) delete[] ptr;
|
|
34
|
+
ptr = np;
|
|
35
|
+
}
|
|
36
|
+
ptr[sz++] = v;
|
|
37
|
+
}
|
|
38
|
+
void reserve(int n) {
|
|
39
|
+
if (n <= cap) return;
|
|
40
|
+
int* np = new int[n];
|
|
41
|
+
std::memcpy(np, ptr, sz * sizeof(int));
|
|
42
|
+
if (ptr != buf) delete[] ptr;
|
|
43
|
+
ptr = np; cap = n;
|
|
44
|
+
}
|
|
45
|
+
void clear() { sz = 0; ptr = buf; cap = INLINE_CAP; }
|
|
46
|
+
int size() const { return sz; }
|
|
47
|
+
int& operator[](int i) { return ptr[i]; }
|
|
48
|
+
int operator[](int i) const { return ptr[i]; }
|
|
49
|
+
int* begin() { return ptr; }
|
|
50
|
+
int* end() { return ptr + sz; }
|
|
51
|
+
~SmallVec() { if (ptr != buf) delete[] ptr; }
|
|
52
|
+
};
|
|
53
|
+
|
|
19
54
|
class NodeCluster;
|
|
20
55
|
struct Point {
|
|
21
56
|
int x;
|
|
@@ -42,7 +77,6 @@ class Node : public Listable {
|
|
|
42
77
|
static const int OCOMPLETE = OMIN | OMAX;
|
|
43
78
|
static const int TURN_MAX = IMAX | OMAX;
|
|
44
79
|
static const int TURN_MIN = IMIN | OMIN;
|
|
45
|
-
const int TURNER[2][2] = {{OMAX, OMIN}, {TURN_MAX, TURN_MIN}};
|
|
46
80
|
static const int OUTER = 0;
|
|
47
81
|
static const int INNER = 1;
|
|
48
82
|
|
|
@@ -60,7 +94,7 @@ class Node : public Listable {
|
|
|
60
94
|
Point start_point, end_point;
|
|
61
95
|
NodeCluster* cluster;
|
|
62
96
|
void add_intersection(Node& other_node, int other_node_index);
|
|
63
|
-
|
|
97
|
+
SmallVec tangs_sequence;
|
|
64
98
|
Point* coords_entering_to(Node *enter_to, int mode, int tracking);
|
|
65
99
|
Node* my_next_outer(Node *last, int versus);
|
|
66
100
|
Node* my_next_inner(Node *last, int versus);
|
|
@@ -35,6 +35,7 @@ NodeCluster::NodeCluster(int h, int w, pf_Options *options) {
|
|
|
35
35
|
this->root_nodes = this->lists.add_list();
|
|
36
36
|
this->inner_plot = this->lists.add_list();
|
|
37
37
|
this->inner_new = this->lists.add_list();
|
|
38
|
+
this->plot_sequence.reserve(1024);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
NodeCluster::~NodeCluster() {
|
|
@@ -68,8 +69,8 @@ void NodeCluster::compress_coords(std::list<Polygon>& polygons, pf_Options optio
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
void NodeCluster::build_tangs_sequence() {
|
|
71
|
-
for (
|
|
72
|
-
for (Node& node :
|
|
72
|
+
for (int y = 0; y < (int)vert_nodes.size(); y++) {
|
|
73
|
+
for (Node& node : vert_nodes[y]) {
|
|
73
74
|
node.precalc_tangs_sequences(*this);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
@@ -94,8 +95,7 @@ Node* NodeCluster::add_node(int min_x, int max_x, int y, char name, int offset)
|
|
|
94
95
|
|
|
95
96
|
while (it != up_nodes.end()) {
|
|
96
97
|
if ((it->min_x - offset) > node.max_x) break;
|
|
97
|
-
|
|
98
|
-
node.add_intersection(*it, current_index);
|
|
98
|
+
node.add_intersection(*it, it->abs_x_index);
|
|
99
99
|
it->add_intersection(node, node.abs_x_index);
|
|
100
100
|
++it;
|
|
101
101
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
Cluster::Cluster(Finder *finder, int height, int start_x, int end_x)
|
|
21
21
|
: finder(finder)
|
|
22
22
|
{ tiles_.reserve(2); // only two (left|right)
|
|
23
|
-
this->hub_ = new Hub(height
|
|
23
|
+
this->hub_ = new Hub(height);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
Cluster::~Cluster() {
|
|
@@ -63,7 +63,6 @@ Tile* Cluster::merge_tiles() {
|
|
|
63
63
|
if (shape->outer_polyline->is_on(Polyline::TRACKED_OUTER) || shape->outer_polyline->width() == 0) continue;
|
|
64
64
|
if (shape->outer_polyline->boundary()) {
|
|
65
65
|
shape->outer_polyline->partition();
|
|
66
|
-
shape->outer_polyline->precalc();
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
68
|
}
|
|
@@ -77,7 +76,7 @@ Tile* Cluster::merge_tiles() {
|
|
|
77
76
|
continue;
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
if (shape->outer_polyline->
|
|
79
|
+
if (shape->outer_polyline->any_ancients) {
|
|
81
80
|
Cursor cursor(*this, shape);
|
|
82
81
|
Sequence* new_outer = nullptr;
|
|
83
82
|
|
|
@@ -56,7 +56,6 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
56
56
|
if (all_parts.empty() || last_part != act_part) {
|
|
57
57
|
all_parts.push_back(act_part);
|
|
58
58
|
}
|
|
59
|
-
|
|
60
59
|
bool jumped_to_new_part = false;
|
|
61
60
|
if (act_part->is(Part::EXCLUSIVE)) {
|
|
62
61
|
if (act_part->size == 0) return;
|
|
@@ -82,36 +81,45 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
82
81
|
}
|
|
83
82
|
this->cluster.positions_pool.emplace_back(this->cluster.hub(), new_position->payload);
|
|
84
83
|
outer_joined_polyline->add(&this->cluster.positions_pool.back());
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
new_position->end_point()->tracked_outer = true;
|
|
85
|
+
int versus = act_part->versus();
|
|
86
|
+
auto& q_set = new_position->end_point()->queues();
|
|
87
|
+
auto it = std::find_if(q_set.begin(), q_set.end(), [&](Queueable<Point>* q) {
|
|
88
|
+
Part* p = static_cast<Part*>(q);
|
|
89
|
+
return p->versus() == -versus && p->polyline()->tile != act_part->polyline()->tile;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
Part* part = nullptr;
|
|
93
|
+
if (it != q_set.end()) {
|
|
94
|
+
part = static_cast<Part*>(*it);
|
|
95
|
+
}
|
|
96
|
+
if (part) {
|
|
97
|
+
const auto n = all_parts.size();
|
|
98
|
+
Part *last_last_part = n >= 2 ? all_parts[n - 2] : nullptr;
|
|
99
|
+
if (last_last_part != part) {
|
|
100
|
+
if (n >= 2) {
|
|
101
|
+
bool all_seam = true;
|
|
102
|
+
for (std::size_t i = all_parts.size() - 2; i < all_parts.size(); ++i) {
|
|
103
|
+
if (all_parts[i]->type != Part::SEAM) {
|
|
104
|
+
all_seam = false;
|
|
105
|
+
break;
|
|
99
106
|
}
|
|
100
|
-
if (all_seam) break;
|
|
101
|
-
}
|
|
102
|
-
if (shapes_sequence_lookup.insert(part->polyline()->shape).second) {
|
|
103
|
-
shapes_sequence.push_back(part->polyline()->shape);
|
|
104
107
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
break;
|
|
108
|
+
if (all_seam) break;
|
|
109
|
+
}
|
|
110
|
+
if (shapes_sequence_lookup.insert(part->polyline()->shape).second) {
|
|
111
|
+
shapes_sequence.push_back(part->polyline()->shape);
|
|
110
112
|
}
|
|
113
|
+
part->next_position(new_position);
|
|
114
|
+
part->dead_end = true;
|
|
115
|
+
act_part = part;
|
|
116
|
+
jumped_to_new_part = true;
|
|
117
|
+
break;
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
|
-
if (jumped_to_new_part)
|
|
114
|
-
|
|
120
|
+
if (!jumped_to_new_part) {
|
|
121
|
+
act_part->next_position(nullptr);
|
|
122
|
+
}
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
125
|
if (jumped_to_new_part) continue;
|
|
@@ -122,107 +130,11 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq) {
|
|
125
|
-
std::vector<InnerPolyline*> retme;
|
|
126
|
-
std::vector<Shape*> missing_shapes;
|
|
127
|
-
|
|
128
|
-
for (Tile *tile : this->cluster.tiles())
|
|
129
|
-
{ for (Shape *shape : tile->shapes())
|
|
130
|
-
{ if (shape->outer_polyline->is_on(Polyline::TRACKED_OUTER) ||
|
|
131
|
-
shape->outer_polyline->is_on(Polyline::TRACKED_INNER) ||
|
|
132
|
-
!shape->outer_polyline->boundary() ||
|
|
133
|
-
std::find(shapes_sequence_.begin(), shapes_sequence_.end(), shape) != shapes_sequence_.end()) {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
missing_shapes.push_back(shape);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (missing_shapes.size() > 0) {
|
|
141
|
-
std::vector<Shape*> to_delay_shapes;
|
|
142
|
-
to_delay_shapes = connect_missings(this->shapes_sequence_, missing_shapes);
|
|
143
|
-
if (!to_delay_shapes.empty())
|
|
144
|
-
{ connect_missings(to_delay_shapes, missing_shapes);
|
|
145
|
-
while (!to_delay_shapes.empty()) {
|
|
146
|
-
to_delay_shapes = connect_missings(this->shapes_sequence_, to_delay_shapes);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
retme = collect_inner_sequences(outer_seq);
|
|
152
|
-
for (Shape* shape : shapes_sequence_) {
|
|
153
|
-
shape->outer_polyline->turn_on(Polyline::TRACKED_INNER);
|
|
154
|
-
}
|
|
155
|
-
return(retme);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
std::vector<Shape*> Cursor::connect_missings(std::vector<Shape*> shapes_sequence, std::vector<Shape*> missing_shapes) {
|
|
159
|
-
std::vector<Shape*> delay_shapes;
|
|
160
|
-
|
|
161
|
-
for (Shape* shape : shapes_sequence)
|
|
162
|
-
{ Polyline* polyline = shape->outer_polyline;
|
|
163
|
-
for (Shape* missing_shape : missing_shapes)
|
|
164
|
-
{ Polyline* outer_polyline = missing_shape->outer_polyline;
|
|
165
|
-
if ( (polyline->mixed_tile_origin == false && outer_polyline->tile == polyline->tile) ||
|
|
166
|
-
outer_polyline->is_on(Polyline::TRACKED_OUTER) ||
|
|
167
|
-
!polyline->vert_intersect(*outer_polyline)) continue;
|
|
168
|
-
std::vector<std::pair<int, int>> intersection = polyline->intersection(outer_polyline);
|
|
169
|
-
if (intersection.size() > 0)
|
|
170
|
-
{ auto result = polyline->sew(intersection, outer_polyline);
|
|
171
|
-
if (!result) {
|
|
172
|
-
delay_shapes.push_back(missing_shape);
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
auto& inject_sequences_left = result->first;
|
|
176
|
-
auto& inject_sequences_right = result->second;
|
|
177
|
-
auto combined = combine(inject_sequences_right, inject_sequences_left);
|
|
178
|
-
for (auto& sewn_sequence : combined) {
|
|
179
|
-
std::vector<Point*> unique;
|
|
180
|
-
unique.reserve(sewn_sequence.size());
|
|
181
|
-
for (Point* p : sewn_sequence) {
|
|
182
|
-
if (!p) continue;
|
|
183
|
-
auto it = std::find_if(
|
|
184
|
-
unique.begin(), unique.end(),
|
|
185
|
-
[&](Point* up) {
|
|
186
|
-
return up && *up == *p;
|
|
187
|
-
});
|
|
188
|
-
if (it == unique.end()) unique.push_back(p);
|
|
189
|
-
}
|
|
190
|
-
sewn_sequence.swap(unique);
|
|
191
|
-
|
|
192
|
-
if (sewn_sequence.size() <= 1) continue;
|
|
193
|
-
|
|
194
|
-
int first_x = sewn_sequence.front()->x;
|
|
195
|
-
bool different_x = false;
|
|
196
|
-
for (Point* p : sewn_sequence) {
|
|
197
|
-
if (p->x != first_x) {
|
|
198
|
-
different_x = true;
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
if (different_x) {
|
|
203
|
-
orphan_inners_.push_back(outer_polyline->tile->shapes_pool->acquire_inner_polyline(sewn_sequence, shape, true));
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
polyline->mixed_tile_origin = true;
|
|
207
|
-
outer_polyline->clear();
|
|
208
|
-
outer_polyline->turn_on(Polyline::TRACKED_OUTER);
|
|
209
|
-
outer_polyline->turn_on(Polyline::TRACKED_INNER);
|
|
210
|
-
orphan_inners_.insert(
|
|
211
|
-
orphan_inners_.end(),
|
|
212
|
-
missing_shape->inner_polylines.begin(),
|
|
213
|
-
missing_shape->inner_polylines.end());
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return(delay_shapes);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
std::vector<InnerPolyline*> Cursor::collect_inner_sequences(Sequence* outer_seq) {
|
|
222
133
|
std::vector<InnerPolyline*> return_inner_polylines;
|
|
223
|
-
|
|
224
|
-
for (
|
|
225
|
-
{
|
|
134
|
+
std::vector<Shape*> processing_queue = shapes_sequence_;
|
|
135
|
+
for (size_t i = 0; i < shapes_sequence_.size(); ++i)
|
|
136
|
+
{ Shape* shape = shapes_sequence_[i];
|
|
137
|
+
Polyline* polyline = shape->outer_polyline;
|
|
226
138
|
for (Part* part : polyline->parts())
|
|
227
139
|
{ if (part->innerable())
|
|
228
140
|
{ std::vector<Part*> all_parts;
|
|
@@ -232,7 +144,6 @@ std::vector<InnerPolyline*> Cursor::collect_inner_sequences(Sequence* outer_seq)
|
|
|
232
144
|
};
|
|
233
145
|
traverse_inner(part, all_parts, bounds);
|
|
234
146
|
Sequence* retme_sequence = shape->outer_polyline->tile->shapes_pool->acquire_sequence();
|
|
235
|
-
|
|
236
147
|
for (Part* part : all_parts)
|
|
237
148
|
{ part->touch();
|
|
238
149
|
retme_sequence->move_from(*part, [&](QNode<Point>* pos) -> bool {
|
|
@@ -242,7 +153,7 @@ std::vector<InnerPolyline*> Cursor::collect_inner_sequences(Sequence* outer_seq)
|
|
|
242
153
|
position->payload->y <= bounds.max)) {
|
|
243
154
|
return(false);
|
|
244
155
|
}
|
|
245
|
-
return(!(polyline->tile->tg_border(*position->payload) && position->end_point()->
|
|
156
|
+
return(!(polyline->tile->tg_border(*position->payload) && position->end_point()->tracked_outer));
|
|
246
157
|
});
|
|
247
158
|
}
|
|
248
159
|
if (retme_sequence->is_not_vertical()) {
|
|
@@ -270,27 +181,39 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
|
|
|
270
181
|
if (act_part->innerable()) {
|
|
271
182
|
all_parts.push_back(act_part);
|
|
272
183
|
bool jumped = false;
|
|
273
|
-
while (act_part = act_part->
|
|
184
|
+
while (act_part = act_part->circular_next) {
|
|
274
185
|
if (act_part->innerable()) {
|
|
275
186
|
all_parts.push_back(act_part);
|
|
276
187
|
} else {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
188
|
+
if (act_part->head)
|
|
189
|
+
{ for (auto dest_part_p : static_cast<Position*>(act_part->head)->end_point()->queues()) {
|
|
190
|
+
Part* dest_part = static_cast<Part*>(dest_part_p);
|
|
191
|
+
if (dest_part->polyline()->tile == act_part->polyline()->tile) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
280
194
|
int dest_part_versus = dest_part->versus();
|
|
281
195
|
if (dest_part_versus != 0 && dest_part_versus == act_part->versus()) continue;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
ins_part->add(&this->cluster.positions_pool.back());
|
|
289
|
-
}
|
|
290
|
-
all_parts.push_back(ins_part);
|
|
196
|
+
std::vector<EndPoint*> link_seq = dest_part->continuum_to(*act_part);
|
|
197
|
+
if (!link_seq.empty()) {
|
|
198
|
+
Part* ins_part = pool.acquire(Part::ADDED, act_part->polyline());
|
|
199
|
+
for (EndPoint* pos : link_seq) {
|
|
200
|
+
this->cluster.positions_pool.emplace_back(pos);
|
|
201
|
+
ins_part->add(&this->cluster.positions_pool.back());
|
|
291
202
|
}
|
|
292
|
-
|
|
293
|
-
|
|
203
|
+
all_parts.push_back(ins_part);
|
|
204
|
+
}
|
|
205
|
+
Shape* shape = dest_part->polyline()->shape;
|
|
206
|
+
if (!dest_part->polyline()->is_on(Polyline::TRACKED_OUTER))
|
|
207
|
+
{ shapes_sequence_.push_back(shape);
|
|
208
|
+
orphan_inners_.insert(
|
|
209
|
+
orphan_inners_.end(),
|
|
210
|
+
shape->inner_polylines.begin(),
|
|
211
|
+
shape->inner_polylines.end());
|
|
212
|
+
}
|
|
213
|
+
dest_part->polyline()->turn_on(Polyline::TRACKED_OUTER);
|
|
214
|
+
if (!dest_part->touched()) {
|
|
215
|
+
dest_part->touch();
|
|
216
|
+
|
|
294
217
|
act_part = dest_part->circular_next;
|
|
295
218
|
jumped = true;
|
|
296
219
|
break;
|
|
@@ -298,7 +221,6 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
|
|
|
298
221
|
}
|
|
299
222
|
if (jumped) break;
|
|
300
223
|
}
|
|
301
|
-
if (jumped) break;
|
|
302
224
|
if (act_part->is(Part::SEAM)) {
|
|
303
225
|
all_parts.push_back(act_part);
|
|
304
226
|
}
|
|
@@ -313,17 +235,3 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
|
|
|
313
235
|
else break;
|
|
314
236
|
}
|
|
315
237
|
}
|
|
316
|
-
|
|
317
|
-
std::vector<std::vector<Point*>> Cursor::combine(std::vector<std::vector<Point*>>& seqa, std::vector<std::vector<Point*>>& seqb)
|
|
318
|
-
{ std::vector<std::vector<Point*>> rets;
|
|
319
|
-
size_t n = std::min(seqa.size(), seqb.size());
|
|
320
|
-
for (size_t i = 0; i < n; ++i) {
|
|
321
|
-
std::vector<Point*> last = std::move(seqa.back());
|
|
322
|
-
seqa.pop_back();
|
|
323
|
-
std::vector<Point*> first = std::move(seqb.front());
|
|
324
|
-
seqb.erase(seqb.begin());
|
|
325
|
-
first.insert(first.end(), last.begin(), last.end());
|
|
326
|
-
rets.push_back(std::move(first));
|
|
327
|
-
}
|
|
328
|
-
return rets;
|
|
329
|
-
}
|
|
@@ -35,8 +35,6 @@ class Cursor {
|
|
|
35
35
|
std::vector<Part*>& all_parts,
|
|
36
36
|
std::vector<Shape*>& shapes_sequence,
|
|
37
37
|
Sequence* outer_joined_polyline);
|
|
38
|
-
std::vector<InnerPolyline*> collect_inner_sequences(Sequence* outer_seq);
|
|
39
38
|
void traverse_inner(Part* act_part, std::vector<Part*> &all_parts, Bounds& bounds);
|
|
40
|
-
std::vector<std::vector<Point*>> combine(std::vector<std::vector<Point*>>& seqa, std::vector<std::vector<Point*>>& seqb);
|
|
41
39
|
std::vector<Shape*> connect_missings(std::vector<Shape*> shapes_sequence, std::vector<Shape*> missing_shapes);
|
|
42
40
|
};
|
|
@@ -20,9 +20,11 @@ class EndPoint {
|
|
|
20
20
|
|
|
21
21
|
void set_point(Point* p) { point_ = p; }
|
|
22
22
|
Point* get_point() const { return point_; }
|
|
23
|
-
std::
|
|
23
|
+
std::vector<Queueable<Point>*>& queues() { return queues_; }
|
|
24
24
|
bool queues_include(Queueable<Point>* q) const;
|
|
25
|
+
bool tracked_outer = false;
|
|
26
|
+
|
|
25
27
|
private:
|
|
26
28
|
Point* point_;
|
|
27
|
-
std::
|
|
29
|
+
std::vector<Queueable<Point>*> queues_;
|
|
28
30
|
};
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
#include "Hub.h"
|
|
11
11
|
#include <cstring>
|
|
12
12
|
|
|
13
|
-
Hub::Hub(int height
|
|
14
|
-
:
|
|
13
|
+
Hub::Hub(int height)
|
|
14
|
+
: height_(height)
|
|
15
15
|
{ size_t total_pixels = static_cast<size_t>(height);
|
|
16
16
|
payloads_.resize(total_pixels);
|
|
17
17
|
bitset_.resize((total_pixels + 63) / 64, 0);
|