initial NFP method with convex polygons working.

This commit is contained in:
tamasmeszaros
2018-06-05 12:02:45 +02:00
parent fd829580e9
commit 34c850fa9d
23 changed files with 3865 additions and 774 deletions

View File

@@ -1,10 +1,11 @@
# Try to find existing GTest installation
find_package(GTest QUIET)
find_package(GTest 1.7)
if(NOT GTEST_FOUND)
message(STATUS "GTest not found so downloading...")
# Go and download google test framework, integrate it with the build
set(GTEST_LIBRARIES gtest gmock)
set(GTEST_LIBS_TO_LINK gtest gtest_main)
if (CMAKE_VERSION VERSION_LESS 3.2)
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
@@ -15,7 +16,7 @@ if(NOT GTEST_FOUND)
include(DownloadProject)
download_project(PROJ googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
GIT_TAG release-1.7.0
${UPDATE_DISCONNECTED_IF_AVAILABLE}
)
@@ -27,22 +28,20 @@ if(NOT GTEST_FOUND)
${googletest_BINARY_DIR}
)
set(GTEST_INCLUDE_DIRS ${googletest_SOURCE_DIR}/include)
else()
include_directories(${GTEST_INCLUDE_DIRS} )
find_package(Threads REQUIRED)
set(GTEST_LIBS_TO_LINK ${GTEST_BOTH_LIBRARIES} Threads::Threads)
endif()
include_directories(BEFORE ${LIBNEST2D_HEADERS})
add_executable(bp2d_tests test.cpp printer_parts.h printer_parts.cpp)
target_link_libraries(bp2d_tests libnest2d
${GTEST_LIBRARIES}
)
add_executable(bp2d_tests test.cpp svgtools.hpp printer_parts.h printer_parts.cpp)
target_link_libraries(bp2d_tests libnest2d_static ${GTEST_LIBS_TO_LINK} )
target_include_directories(bp2d_tests PRIVATE BEFORE ${LIBNEST2D_HEADERS}
${GTEST_INCLUDE_DIRS})
if(DEFINED LIBNEST2D_TEST_LIBRARIES)
target_link_libraries(bp2d_tests ${LIBNEST2D_TEST_LIBRARIES})
endif()
add_test(gtests bp2d_tests)
add_executable(main EXCLUDE_FROM_ALL main.cpp printer_parts.cpp printer_parts.h)
target_link_libraries(main libnest2d)
target_include_directories(main PUBLIC ${CMAKE_SOURCE_DIR})
add_test(libnest2d_tests bp2d_tests)

View File

@@ -7,232 +7,56 @@
#include "printer_parts.h"
#include "benchmark.h"
#include "svgtools.hpp"
namespace {
using namespace libnest2d;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
//using PackGroup = std::vector<ItemGroup>;
template<int SCALE, class Bin >
void exportSVG(PackGroup& result, const Bin& bin) {
std::string loc = "out";
static std::string svg_header =
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
)raw";
int i = 0;
for(auto r : result) {
std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out);
if(out.is_open()) {
out << svg_header;
Item rbin( Rectangle(bin.width(), bin.height()) );
for(unsigned i = 0; i < rbin.vertexCount(); i++) {
auto v = rbin.vertex(i);
setY(v, -getY(v)/SCALE + 500 );
setX(v, getX(v)/SCALE);
rbin.setVertex(i, v);
}
out << ShapeLike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
for(Item& sh : r) {
Item tsh(sh.transformedShape());
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
auto v = tsh.vertex(i);
setY(v, -getY(v)/SCALE + 500);
setX(v, getX(v)/SCALE);
tsh.setVertex(i, v);
}
out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
}
out << "\n</svg>" << std::endl;
}
out.close();
i++;
std::vector<Item>& _parts(std::vector<Item>& ret, const TestData& data)
{
if(ret.empty()) {
ret.reserve(data.size());
for(auto& inp : data)
ret.emplace_back(inp);
}
return ret;
}
template< int SCALE, class Bin>
void exportSVG(ItemGroup& result, const Bin& bin, int idx) {
std::string loc = "out";
static std::string svg_header =
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
)raw";
int i = idx;
auto r = result;
// for(auto r : result) {
std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out);
if(out.is_open()) {
out << svg_header;
Item rbin( Rectangle(bin.width(), bin.height()) );
for(unsigned i = 0; i < rbin.vertexCount(); i++) {
auto v = rbin.vertex(i);
setY(v, -getY(v)/SCALE + 500 );
setX(v, getX(v)/SCALE);
rbin.setVertex(i, v);
}
out << ShapeLike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
for(Item& sh : r) {
Item tsh(sh.transformedShape());
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
auto v = tsh.vertex(i);
setY(v, -getY(v)/SCALE + 500);
setX(v, getX(v)/SCALE);
tsh.setVertex(i, v);
}
out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
}
out << "\n</svg>" << std::endl;
}
out.close();
// i++;
// }
}
std::vector<Item>& prusaParts() {
static std::vector<Item> ret;
return _parts(ret, PRINTER_PART_POLYGONS);
}
void findDegenerateCase() {
using namespace libnest2d;
auto input = PRINTER_PART_POLYGONS;
auto scaler = [](Item& item) {
for(unsigned i = 0; i < item.vertexCount(); i++) {
auto v = item.vertex(i);
setX(v, 100*getX(v)); setY(v, 100*getY(v));
item.setVertex(i, v);
}
};
auto cmp = [](const Item& t1, const Item& t2) {
return t1.area() > t2.area();
};
std::for_each(input.begin(), input.end(), scaler);
std::sort(input.begin(), input.end(), cmp);
Box bin(210*100, 250*100);
BottomLeftPlacer placer(bin);
auto it = input.begin();
auto next = it;
int i = 0;
while(it != input.end() && ++next != input.end()) {
placer.pack(*it);
placer.pack(*next);
auto result = placer.getItems();
bool valid = true;
if(result.size() == 2) {
Item& r1 = result[0];
Item& r2 = result[1];
valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
if(!valid) {
std::cout << "error index: " << i << std::endl;
exportSVG<100>(result, bin, i);
}
} else {
std::cout << "something went terribly wrong!" << std::endl;
}
placer.clearItems();
it++;
i++;
}
std::vector<Item>& stegoParts() {
static std::vector<Item> ret;
return _parts(ret, STEGOSAUR_POLYGONS);
}
void arrangeRectangles() {
using namespace libnest2d;
// std::vector<Rectangle> input = {
// {80, 80},
// {110, 10},
// {200, 5},
// {80, 30},
// {60, 90},
// {70, 30},
// {80, 60},
// {60, 60},
// {60, 40},
// {40, 40},
// {10, 10},
// {10, 10},
// {10, 10},
// {10, 10},
// {10, 10},
// {5, 5},
// {5, 5},
// {5, 5},
// {5, 5},
// {5, 5},
// {5, 5},
// {5, 5},
// {20, 20},
// {80, 80},
// {110, 10},
// {200, 5},
// {80, 30},
// {60, 90},
// {70, 30},
// {80, 60},
// {60, 60},
// {60, 40},
// {40, 40},
// {10, 10},
// {10, 10},
// {10, 10},
// {10, 10},
// {10, 10},
// {5, 5},
// {5, 5},
// {5, 5},
// {5, 5},
// {5, 5},
// {5, 5},
// {5, 5},
// {20, 20}
// };
auto input = PRINTER_PART_POLYGONS;
auto input = stegoParts();
const int SCALE = 1000000;
// const int SCALE = 1;
Box bin(210*SCALE, 250*SCALE);
auto scaler = [&SCALE, &bin](Item& item) {
// double max_area = 0;
for(unsigned i = 0; i < item.vertexCount(); i++) {
auto v = item.vertex(i);
setX(v, SCALE*getX(v)); setY(v, SCALE*getY(v));
item.setVertex(i, v);
// double area = item.area();
// if(max_area < area) {
// max_area = area;
// bin = item.boundingBox();
// }
}
};
Coord min_obj_distance = 0; //6*SCALE;
Coord min_obj_distance = 2*SCALE;
NfpPlacer::Config pconf;
pconf.alignment = NfpPlacer::Config::Alignment::TOP_LEFT;
Arranger<NfpPlacer, DJDHeuristic> arrange(bin, min_obj_distance, pconf);
std::for_each(input.begin(), input.end(), scaler);
Arranger<BottomLeftPlacer, DJDHeuristic> arrange(bin, min_obj_distance);
// arrange.progressIndicator([&arrange, &bin](unsigned r){
// svg::SVGWriter::Config conf;
// conf.mm_in_coord_units = SCALE;
// svg::SVGWriter svgw(conf);
// svgw.setSize(bin);
// svgw.writePackGroup(arrange.lastResult());
// svgw.save("out");
// std::cout << "Remaining items: " << r << std::endl;
// });
Benchmark bench;
@@ -249,8 +73,12 @@ void arrangeRectangles() {
std::cout << ret.second << std::endl;
}
exportSVG<SCALE>(result, bin);
svg::SVGWriter::Config conf;
conf.mm_in_coord_units = SCALE;
svg::SVGWriter svgw(conf);
svgw.setSize(bin);
svgw.writePackGroup(result);
svgw.save("out");
}
int main(void /*int argc, char **argv*/) {

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,11 @@
#define PRINTER_PARTS_H
#include <vector>
#include <libnest2d.h>
#include <clipper.hpp>
extern const std::vector<libnest2d::Item> PRINTER_PART_POLYGONS;
using TestData = std::vector<ClipperLib::Path>;
extern const TestData PRINTER_PART_POLYGONS;
extern const TestData STEGOSAUR_POLYGONS;
#endif // PRINTER_PARTS_H

View File

@@ -0,0 +1,112 @@
#ifndef SVGTOOLS_HPP
#define SVGTOOLS_HPP
#include <iostream>
#include <fstream>
#include <string>
#include <libnest2d.h>
#include <libnest2d/geometries_io.hpp>
namespace libnest2d { namespace svg {
class SVGWriter {
public:
enum OrigoLocation {
TOPLEFT,
BOTTOMLEFT
};
struct Config {
OrigoLocation origo_location;
Coord mm_in_coord_units;
double width, height;
Config():
origo_location(BOTTOMLEFT), mm_in_coord_units(1000000),
width(500), height(500) {}
};
private:
Config conf_;
std::vector<std::string> svg_layers_;
bool finished_ = false;
public:
SVGWriter(const Config& conf = Config()):
conf_(conf) {}
void setSize(const Box& box) {
conf_.height = static_cast<double>(box.height()) /
conf_.mm_in_coord_units;
conf_.width = static_cast<double>(box.width()) /
conf_.mm_in_coord_units;
}
void writeItem(const Item& item) {
if(svg_layers_.empty()) addLayer();
Item tsh(item.transformedShape());
if(conf_.origo_location == BOTTOMLEFT)
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
auto v = tsh.vertex(i);
setY(v, -getY(v) + conf_.height*conf_.mm_in_coord_units);
tsh.setVertex(i, v);
}
currentLayer() += ShapeLike::serialize<Formats::SVG>(tsh.rawShape(),
1.0/conf_.mm_in_coord_units) + "\n";
}
void writePackGroup(const PackGroup& result) {
for(auto r : result) {
addLayer();
for(Item& sh : r) {
writeItem(sh);
}
finishLayer();
}
}
void addLayer() {
svg_layers_.emplace_back(header());
finished_ = false;
}
void finishLayer() {
currentLayer() += "\n</svg>\n";
finished_ = true;
}
void save(const std::string& filepath) {
unsigned lyrc = svg_layers_.size() > 1? 1 : 0;
unsigned last = svg_layers_.size() > 1? svg_layers_.size() : 0;
for(auto& lyr : svg_layers_) {
std::fstream out(filepath + (lyrc > 0? std::to_string(lyrc) : "") +
".svg", std::fstream::out);
if(out.is_open()) out << lyr;
if(lyrc == last && !finished_) out << "\n</svg>\n";
out.flush(); out.close(); lyrc++;
};
}
private:
std::string& currentLayer() { return svg_layers_.back(); }
const std::string header() const {
std::string svg_header =
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height=")raw";
svg_header += std::to_string(conf_.height) + "\" width=\"" + std::to_string(conf_.width) + "\" ";
svg_header += R"raw(xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">)raw";
return svg_header;
}
};
}
}
#endif // SVGTOOLS_HPP

View File

@@ -1,5 +1,4 @@
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include <gtest/gtest.h>
#include <fstream>
#include <libnest2d.h>
@@ -7,6 +6,17 @@
#include <libnest2d/geometries_io.hpp>
#include <libnest2d/geometries_nfp.hpp>
std::vector<libnest2d::Item>& prusaParts() {
static std::vector<libnest2d::Item> ret;
if(ret.empty()) {
ret.reserve(PRINTER_PART_POLYGONS.size());
for(auto& inp : PRINTER_PART_POLYGONS) ret.emplace_back(inp);
}
return ret;
}
TEST(BasicFunctionality, Angles)
{
@@ -24,6 +34,44 @@ TEST(BasicFunctionality, Angles)
ASSERT_TRUE(rad == deg);
Segment seg = {{0, 0}, {12, -10}};
ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 270 &&
Degrees(seg.angleToXaxis()) < 360);
seg = {{0, 0}, {12, 10}};
ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 0 &&
Degrees(seg.angleToXaxis()) < 90);
seg = {{0, 0}, {-12, 10}};
ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 90 &&
Degrees(seg.angleToXaxis()) < 180);
seg = {{0, 0}, {-12, -10}};
ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 180 &&
Degrees(seg.angleToXaxis()) < 270);
seg = {{0, 0}, {1, 0}};
ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 0);
seg = {{0, 0}, {0, 1}};
ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 90);
seg = {{0, 0}, {-1, 0}};
ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 180);
seg = {{0, 0}, {0, -1}};
ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 270);
}
// Simple test, does not use gmock
@@ -33,21 +81,21 @@ TEST(BasicFunctionality, creationAndDestruction)
Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} };
ASSERT_EQ(sh.vertexCount(), 4);
ASSERT_EQ(sh.vertexCount(), 4u);
Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} });
ASSERT_EQ(sh2.vertexCount(), 4);
ASSERT_EQ(sh2.vertexCount(), 4u);
// copy
Item sh3 = sh2;
ASSERT_EQ(sh3.vertexCount(), 4);
ASSERT_EQ(sh3.vertexCount(), 4u);
sh2 = {};
ASSERT_EQ(sh2.vertexCount(), 0);
ASSERT_EQ(sh3.vertexCount(), 4);
ASSERT_EQ(sh2.vertexCount(), 0u);
ASSERT_EQ(sh3.vertexCount(), 4u);
}
@@ -70,7 +118,8 @@ TEST(GeometryAlgorithms, Distance) {
auto check = [](Coord val, Coord expected) {
if(std::is_floating_point<Coord>::value)
ASSERT_DOUBLE_EQ(static_cast<double>(val), expected);
ASSERT_DOUBLE_EQ(static_cast<double>(val),
static_cast<double>(expected));
else
ASSERT_EQ(val, expected);
};
@@ -112,6 +161,18 @@ TEST(GeometryAlgorithms, Area) {
ASSERT_EQ(rect2.area(), 10000);
Item item = {
{61, 97},
{70, 151},
{176, 151},
{189, 138},
{189, 59},
{70, 59},
{61, 77},
{61, 97}
};
ASSERT_TRUE(ShapeLike::area(item.transformedShape()) > 0 );
}
TEST(GeometryAlgorithms, IsPointInsidePolygon) {
@@ -240,7 +301,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight)
auto groups = arrange(rects.begin(), rects.end());
ASSERT_EQ(groups.size(), 1);
ASSERT_EQ(groups.size(), 1u);
ASSERT_EQ(groups[0].size(), rects.size());
// check for no intersections, no containment:
@@ -294,7 +355,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
auto groups = arrange(rects.begin(), rects.end());
ASSERT_EQ(groups.size(), 1);
ASSERT_EQ(groups.size(), 1u);
ASSERT_EQ(groups[0].size(), rects.size());
// check for no intersections, no containment:
@@ -363,7 +424,7 @@ R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
TEST(GeometryAlgorithms, BottomLeftStressTest) {
using namespace libnest2d;
auto input = PRINTER_PART_POLYGONS;
auto& input = prusaParts();
Box bin(210, 250);
BottomLeftPlacer placer(bin);
@@ -399,73 +460,240 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) {
}
}
namespace {
struct ItemPair {
Item orbiter;
Item stationary;
};
std::vector<ItemPair> nfp_testdata = {
{
{
{80, 50},
{100, 70},
{120, 50},
{80, 50}
},
{
{10, 10},
{10, 40},
{40, 40},
{40, 10},
{10, 10}
}
},
{
{
{80, 50},
{60, 70},
{80, 90},
{120, 90},
{140, 70},
{120, 50},
{80, 50}
},
{
{10, 10},
{10, 40},
{40, 40},
{40, 10},
{10, 10}
}
},
{
{
{40, 10},
{30, 10},
{20, 20},
{20, 30},
{30, 40},
{40, 40},
{50, 30},
{50, 20},
{40, 10}
},
{
{80, 0},
{80, 30},
{110, 30},
{110, 0},
{80, 0}
}
},
{
{
{117, 107},
{118, 109},
{120, 112},
{122, 113},
{128, 113},
{130, 112},
{132, 109},
{133, 107},
{133, 103},
{132, 101},
{130, 98},
{128, 97},
{122, 97},
{120, 98},
{118, 101},
{117, 103},
{117, 107}
},
{
{102, 116},
{111, 126},
{114, 126},
{144, 106},
{148, 100},
{148, 85},
{147, 84},
{102, 84},
{102, 116},
}
},
{
{
{99, 122},
{108, 140},
{110, 142},
{139, 142},
{151, 122},
{151, 102},
{142, 70},
{139, 68},
{111, 68},
{108, 70},
{99, 102},
{99, 122},
},
{
{107, 124},
{128, 125},
{133, 125},
{136, 124},
{140, 121},
{142, 119},
{143, 116},
{143, 109},
{141, 93},
{139, 89},
{136, 86},
{134, 85},
{108, 85},
{107, 86},
{107, 124},
}
},
{
{
{91, 100},
{94, 144},
{117, 153},
{118, 153},
{159, 112},
{159, 110},
{156, 66},
{133, 57},
{132, 57},
{91, 98},
{91, 100},
},
{
{101, 90},
{103, 98},
{107, 113},
{114, 125},
{115, 126},
{135, 126},
{136, 125},
{144, 114},
{149, 90},
{149, 89},
{148, 87},
{145, 84},
{105, 84},
{102, 87},
{101, 89},
{101, 90},
}
}
};
}
TEST(GeometryAlgorithms, nfpConvexConvex) {
using namespace libnest2d;
const unsigned long SCALE = 1;
const Coord SCALE = 1000000;
Box bin(210*SCALE, 250*SCALE);
Item stationary = {
{120, 114},
{130, 114},
{130, 103},
{128, 96},
{122, 96},
{120, 103},
{120, 114}
};
int testcase = 0;
Item orbiter = {
{72, 147},
{94, 151},
{178, 151},
{178, 59},
{72, 59},
{72, 147}
};
auto& exportfun = exportSVG<1, Box>;
orbiter.translate({210*SCALE, 0});
auto onetest = [&](Item& orbiter, Item& stationary){
testcase++;
auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(),
orbiter.transformedShape());
orbiter.translate({210*SCALE, 0});
auto v = ShapeLike::isValid(nfp);
auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(),
orbiter.transformedShape());
if(!v.first) {
std::cout << v.second << std::endl;
}
auto v = ShapeLike::isValid(nfp);
ASSERT_TRUE(v.first);
Item infp(nfp);
int i = 0;
auto rorbiter = orbiter.transformedShape();
auto vo = *(ShapeLike::begin(rorbiter));
for(auto v : infp) {
auto dx = getX(v) - getX(vo);
auto dy = getY(v) - getY(vo);
Item tmp = orbiter;
tmp.translate({dx, dy});
bool notinside = !tmp.isInside(stationary);
bool notintersecting = !Item::intersects(tmp, stationary);
if(!(notinside && notintersecting)) {
std::vector<std::reference_wrapper<Item>> inp = {
std::ref(stationary), std::ref(tmp), std::ref(infp)
};
exportSVG<SCALE>(inp, bin, i++);
if(!v.first) {
std::cout << v.second << std::endl;
}
//ASSERT_TRUE(notintersecting);
ASSERT_TRUE(notinside);
ASSERT_TRUE(v.first);
Item infp(nfp);
int i = 0;
auto rorbiter = orbiter.transformedShape();
auto vo = Nfp::referenceVertex(rorbiter);
ASSERT_TRUE(stationary.isInside(infp));
for(auto v : infp) {
auto dx = getX(v) - getX(vo);
auto dy = getY(v) - getY(vo);
Item tmp = orbiter;
tmp.translate({dx, dy});
bool notinside = !tmp.isInside(stationary);
bool notintersecting = !Item::intersects(tmp, stationary) ||
Item::touches(tmp, stationary);
if(!(notinside && notintersecting)) {
std::vector<std::reference_wrapper<Item>> inp = {
std::ref(stationary), std::ref(tmp), std::ref(infp)
};
exportfun(inp, bin, testcase*i++);
}
ASSERT_TRUE(notintersecting);
ASSERT_TRUE(notinside);
}
};
for(auto& td : nfp_testdata) {
auto orbiter = td.orbiter;
auto stationary = td.stationary;
onetest(orbiter, stationary);
}
for(auto& td : nfp_testdata) {
auto orbiter = td.stationary;
auto stationary = td.orbiter;
onetest(orbiter, stationary);
}
}
int main(int argc, char **argv) {