Adding rotating calipers algorithm for minimum are bounding box rotation.

Cleanup, fix build on windows and add test for rotcalipers.

Try to fix compilation on windows

With updates from libnest2d
Another build fix.


Clean up and add comments.


adding rotcalipers test  and some cleanup


Trying to fix on OSX


Fix rotcalipers array indexing


Get rid of boost convex hull.


Adding helper function 'remove_collinear_points'


Importing new libnest2d upgrades.


Disable using __int128 in NFP on OSX
This commit is contained in:
tamasmeszaros 2019-06-06 14:27:07 +02:00
parent 6136fe7d92
commit d4fe7b5a96
25 changed files with 1272 additions and 856 deletions

View File

@ -48,6 +48,9 @@ set(LIBNEST2D_SRCFILES
${SRC_DIR}/libnest2d/optimizer.hpp
${SRC_DIR}/libnest2d/utils/metaloop.hpp
${SRC_DIR}/libnest2d/utils/rotfinder.hpp
${SRC_DIR}/libnest2d/utils/rotcalipers.hpp
${SRC_DIR}/libnest2d/utils/bigint.hpp
${SRC_DIR}/libnest2d/utils/rational.hpp
${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp
${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp
${SRC_DIR}/libnest2d/placers/nfpplacer.hpp
@ -70,12 +73,10 @@ if(TBB_FOUND)
# The Intel TBB library will use the std::exception_ptr feature of C++11.
target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0)
target_link_libraries(libnest2d INTERFACE tbb)
# The following breaks compilation on Visual Studio in Debug mode.
#find_package(Threads REQUIRED)
#target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS}
# Threads::Threads
# )
find_package(Threads REQUIRED)
target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS}
Threads::Threads
)
else()
find_package(OpenMP QUIET)
@ -92,10 +93,11 @@ endif()
add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES})
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend)
add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER})
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer)
#target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
# target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
target_include_directories(libnest2d INTERFACE ${SRC_DIR})
if(NOT LIBNEST2D_HEADER_ONLY)

View File

@ -47,6 +47,17 @@ using NfpPlacer = _NfpPlacer<Box>;
// This supports only box shaped bins
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
#ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template PackGroup Nester<NfpPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
@ -60,19 +71,6 @@ PackGroup nest(Iterator from, Iterator to,
return nester.execute(from, to);
}
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
PackGroup nest(Container&& cont,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(),
bin, dist, pconf, sconf);
}
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
@ -90,6 +88,42 @@ PackGroup nest(Iterator from, Iterator to,
return nester.execute(from, to);
}
#ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
extern template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
PackGroup nest(Container&& cont,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(),
bin, dist, pconf, sconf);
}
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
@ -105,71 +139,6 @@ PackGroup nest(Container&& cont,
bin, prg, scond, dist, pconf, sconf);
}
#ifdef LIBNEST2D_STATIC
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>(
std::vector<Item>& cont,
const Box& bin,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>(
std::vector<Item>& cont,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>(
std::vector<Item>&& cont,
const Box& bin,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>(
std::vector<Item>&& cont,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>(
std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>(
std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
#endif
}
#endif // LIBNEST2D_H

View File

@ -33,19 +33,18 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a
# ${clipper_library_BINARY_DIR}
# )
add_library(ClipperBackend STATIC
add_library(clipperBackend STATIC
${clipper_library_SOURCE_DIR}/clipper.cpp
${clipper_library_SOURCE_DIR}/clipper.hpp)
target_include_directories(ClipperBackend INTERFACE
${clipper_library_SOURCE_DIR})
target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR})
else()
message(FATAL_ERROR "Can't find clipper library and no SVN client found to download.
You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.")
endif()
else()
add_library(ClipperBackend INTERFACE)
target_link_libraries(ClipperBackend INTERFACE Clipper::Clipper)
add_library(clipperBackend INTERFACE)
target_link_libraries(clipperBackend INTERFACE Clipper::Clipper)
endif()
else()
# set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE)
@ -69,6 +68,6 @@ target_include_directories(clipperBackend SYSTEM INTERFACE ${Boost_INCLUDE_DIRS}
target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
# And finally plug the ClipperBackend into libnest2d
#target_link_libraries(libnest2d INTERFACE ClipperBackend)
# And finally plug the clipperBackend into libnest2d
# target_link_libraries(libnest2d INTERFACE clipperBackend)

View File

@ -12,13 +12,13 @@ struct Polygon {
inline Polygon() = default;
inline explicit Polygon(const Path& cont): Contour(cont) {}
inline explicit Polygon(const Paths& holes):
Holes(holes) {}
// inline explicit Polygon(const Paths& holes):
// Holes(holes) {}
inline Polygon(const Path& cont, const Paths& holes):
Contour(cont), Holes(holes) {}
inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {}
inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
// inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
inline Polygon(Path&& cont, Paths&& holes):
Contour(std::move(cont)), Holes(std::move(holes)) {}
};
@ -42,7 +42,7 @@ inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) {
return p;
}
inline IntPoint operator -(IntPoint& p ) {
inline IntPoint operator -(const IntPoint& p ) {
IntPoint ret = p;
ret.X = -ret.X;
ret.Y = -ret.Y;

View File

@ -20,43 +20,23 @@ using PathImpl = ClipperLib::Path;
using HoleStore = ClipperLib::Paths;
using PolygonImpl = ClipperLib::Polygon;
// Type of coordinate units used by Clipper
template<> struct CoordType<PointImpl> {
using Type = ClipperLib::cInt;
};
// Type of point used by Clipper
template<> struct PointType<PolygonImpl> {
using Type = PointImpl;
};
template<> struct PointType<PathImpl> {
using Type = PointImpl;
};
template<> struct PointType<PointImpl> {
using Type = PointImpl;
};
template<> struct CountourType<PolygonImpl> {
using Type = PathImpl;
};
template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; };
template<> struct ShapeTag<PathImpl> { using Type = PathTag; };
template<> struct ShapeTag<PointImpl> { using Type = PointTag; };
template<> struct ShapeTag<PathImpl> { using Type = PathTag; };
template<> struct ShapeTag<PointImpl> { using Type = PointTag; };
template<> struct ShapeTag<TMultiShape<PolygonImpl>> {
using Type = MultiPolygonTag;
};
// Type of coordinate units used by Clipper. Enough to specialize for point,
// the rest of the types will work (Path, Polygon)
template<> struct CoordType<PointImpl> { using Type = ClipperLib::cInt; };
template<> struct PointType<TMultiShape<PolygonImpl>> {
using Type = PointImpl;
};
// Enough to specialize for path, it will work for multishape and Polygon
template<> struct PointType<PathImpl> { using Type = PointImpl; };
template<> struct HolesContainer<PolygonImpl> {
using Type = ClipperLib::Paths;
};
// This is crucial. CountourType refers to itself by default, so we don't have
// to secialize for clipper Path. ContourType<PathImpl>::Type is PathImpl.
template<> struct ContourType<PolygonImpl> { using Type = PathImpl; };
// The holes are contained in Clipper::Paths
template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths; };
namespace pointlike {
@ -86,39 +66,11 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p)
}
// Using the libnest2d default area implementation
#define DISABLE_BOOST_AREA
namespace _smartarea {
template<Orientation o>
inline double area(const PolygonImpl& /*sh*/) {
return std::nan("");
}
template<>
inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
return std::accumulate(sh.Holes.begin(), sh.Holes.end(),
ClipperLib::Area(sh.Contour),
[](double a, const ClipperLib::Path& pt){
return a + ClipperLib::Area(pt);
});
}
template<>
inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) {
return -area<Orientation::COUNTER_CLOCKWISE>(sh);
}
}
namespace shapelike {
// Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline double area(const PolygonImpl& sh, const PolygonTag&)
{
return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
}
template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
{
#define DISABLE_BOOST_OFFSET
@ -200,43 +152,16 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
{
PolygonImpl p;
p.Contour = path;
// Expecting that the coordinate system Y axis is positive in upwards
// direction
if(ClipperLib::Orientation(p.Contour)) {
// Not clockwise then reverse the b*tch
ClipperLib::ReversePath(p.Contour);
}
p.Holes = holes;
for(auto& h : p.Holes) {
if(!ClipperLib::Orientation(h)) {
ClipperLib::ReversePath(h);
}
}
return p;
}
template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) {
PolygonImpl p;
p.Contour.swap(path);
// Expecting that the coordinate system Y axis is positive in upwards
// direction
if(ClipperLib::Orientation(p.Contour)) {
// Not clockwise then reverse the b*tch
ClipperLib::ReversePath(p.Contour);
}
p.Holes.swap(holes);
for(auto& h : p.Holes) {
if(!ClipperLib::Orientation(h)) {
ClipperLib::ReversePath(h);
}
}
return p;
}
@ -314,13 +239,13 @@ inline void rotate(PolygonImpl& sh, const Radians& rads)
} // namespace shapelike
#define DISABLE_BOOST_NFP_MERGE
inline std::vector<PolygonImpl> clipper_execute(
inline TMultiShape<PolygonImpl> clipper_execute(
ClipperLib::Clipper& clipper,
ClipperLib::ClipType clipType,
ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd,
ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd)
{
shapelike::Shapes<PolygonImpl> retv;
TMultiShape<PolygonImpl> retv;
ClipperLib::PolyTree result;
clipper.Execute(clipType, result, subjFillType, clipFillType);
@ -370,8 +295,8 @@ inline std::vector<PolygonImpl> clipper_execute(
namespace nfp {
template<> inline std::vector<PolygonImpl>
merge(const std::vector<PolygonImpl>& shapes)
template<> inline TMultiShape<PolygonImpl>
merge(const TMultiShape<PolygonImpl>& shapes)
{
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
@ -394,6 +319,8 @@ merge(const std::vector<PolygonImpl>& shapes)
}
#define DISABLE_BOOST_CONVEX_HULL
//#define DISABLE_BOOST_SERIALIZE
//#define DISABLE_BOOST_UNSERIALIZE

View File

@ -9,6 +9,7 @@
#include <string>
#include <cmath>
#include <type_traits>
#include <limits>
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
#define BP2D_NOEXCEPT
@ -197,6 +198,33 @@ public:
}
};
struct ScalarTag {};
struct BigIntTag {};
struct RationalTag {};
template<class T> struct _NumTag {
using Type =
enable_if_t<std::is_arithmetic<T>::value, ScalarTag>;
};
template<class T> using NumTag = typename _NumTag<remove_cvref_t<T>>::Type;
/// A local version for abs that is garanteed to work with libnest2d types
template <class T> inline T abs(const T& v, ScalarTag)
{
return std::abs(v);
}
template<class T> inline T abs(const T& v) { return abs(v, NumTag<T>()); }
template<class T2, class T1> inline T2 cast(const T1& v, ScalarTag, ScalarTag)
{
return static_cast<T2>(v);
}
template<class T2, class T1> inline T2 cast(const T1& v) {
return cast<T2, T1>(v, NumTag<T1>(), NumTag<T2>());
}
}
#endif // LIBNEST2D_CONFIG_HPP

View File

@ -7,45 +7,125 @@
#include <array>
#include <vector>
#include <numeric>
#include <limits>
#include <iterator>
#include <cmath>
#include <cstdint>
#include "common.hpp"
#include <libnest2d/common.hpp>
namespace libnest2d {
// Meta tags for different geometry concepts.
struct PointTag {};
struct PolygonTag {};
struct PathTag {};
struct MultiPolygonTag {};
struct BoxTag {};
struct CircleTag {};
/// Meta-function to derive the tag of a shape type.
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
/// Tag<S> will be used instead of `typename ShapeTag<S>::Type`
template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type;
/// Meta function to derive the contour type for a polygon which could be itself
template<class RawShape> struct ContourType { using Type = RawShape; };
/// TContour<RawShape> instead of `typename ContourType<RawShape>::type`
template<class RawShape>
using TContour = typename ContourType<remove_cvref_t<RawShape>>::Type;
/// Getting the type of point structure used by a shape.
template<class Sh> struct PointType {
using Type = typename PointType<TContour<Sh>>::Type;
};
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
template<class Shape>
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
/// Getting the coordinate data type for a geometry class.
template<class GeomClass> struct CoordType { using Type = long; };
template<class GeomClass> struct CoordType {
using Type = typename CoordType<TPoint<GeomClass>>::Type;
};
/// TCoord<GeomType> as shorthand for typename `CoordType<GeomType>::Type`.
template<class GeomType>
using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
/// Getting the type of point structure used by a shape.
template<class Sh> struct PointType { using Type = typename Sh::PointType; };
/// Getting the computation type for a certain geometry type.
/// It is the coordinate type by default but it is advised that a type with
/// larger precision and (or) range is specified.
template<class T, bool = std::is_arithmetic<T>::value> struct ComputeType {};
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
template<class Shape>
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
/// A compute type is introduced to hold the results of computations on
/// coordinates and points. It should be larger in range than the coordinate
/// type or the range of coordinates should be limited to not loose precision.
template<class GeomClass> struct ComputeType<GeomClass, false> {
using Type = typename ComputeType<TCoord<GeomClass>>::Type;
};
/// libnest2d will choose a default compute type for various coordinate types
/// if the backend has not specified anything.
template<class T> struct DoublePrecision { using Type = T; };
template<> struct DoublePrecision<int8_t> { using Type = int16_t; };
template<> struct DoublePrecision<int16_t> { using Type = int32_t; };
template<> struct DoublePrecision<int32_t> { using Type = int64_t; };
template<> struct DoublePrecision<float> { using Type = double; };
template<> struct DoublePrecision<double> { using Type = long double; };
template<class I> struct ComputeType<I, true> {
using Type = typename DoublePrecision<I>::Type;
};
template<class RawShape> struct CountourType { using Type = RawShape; };
template<class RawShape>
using TContour = typename CountourType<remove_cvref_t<RawShape>>::Type;
/// TCompute<T> shorthand for `typename ComputeType<T>::Type`
template<class T> using TCompute = typename ComputeType<remove_cvref_t<T>>::Type;
/// A meta function to derive a container type for holes in a polygon
template<class RawShape>
struct HolesContainer { using Type = std::vector<TContour<RawShape>>; };
/// Shorthand for `typename HolesContainer<RawShape>::Type`
template<class RawShape>
using THolesContainer = typename HolesContainer<remove_cvref_t<RawShape>>::Type;
/*
* TContour, TPoint, TCoord and TCompute should be usable for any type for which
* it makes sense. For example, the point type could be derived from the contour,
* the polygon and (or) the multishape as well. The coordinate type also and
* including the point type. TCoord<Polygon>, TCoord<Path>, TCoord<Point> are
* all valid types and derives the coordinate type of template argument Polygon,
* Path and Point. This is also true for TCompute, but it can also take the
* coordinate type as argument.
*/
template<class RawShape>
struct LastPointIsFirst { static const bool Value = true; };
/*
* A Multi shape concept is also introduced. A multi shape is something that
* can contain the result of an operation where the input is one polygon and
* the result could be many polygons or path -> paths. The MultiShape should be
* a container type. If the backend does not specialize the MultiShape template,
* a default multi shape container will be used.
*/
/// The default multi shape container.
template<class S> struct DefaultMultiShape: public std::vector<S> {
using Tag = MultiPolygonTag;
template<class...Args> DefaultMultiShape(Args&&...args):
std::vector<S>(std::forward<Args>(args)...) {}
};
/// The MultiShape Type trait which gets the container type for a geometry type.
template<class S> struct MultiShape { using Type = DefaultMultiShape<S>; };
/// use TMultiShape<S> instead of `typename MultiShape<S>::Type`
template<class S>
using TMultiShape = typename MultiShape<remove_cvref_t<S>>::Type;
// A specialization of ContourType to work with the default multishape type
template<class S> struct ContourType<DefaultMultiShape<S>> {
using Type = typename ContourType<S>::Type;
};
enum class Orientation {
CLOCKWISE,
@ -59,6 +139,11 @@ struct OrientationType {
static const Orientation Value = Orientation::CLOCKWISE;
};
template<class T> inline /*constexpr*/ bool is_clockwise() {
return OrientationType<TContour<T>>::Value == Orientation::CLOCKWISE;
}
/**
* \brief A point pair base class for other point pairs (segment, box, ...).
* \tparam RawPoint The actual point type to use.
@ -69,21 +154,6 @@ struct PointPair {
RawPoint p2;
};
struct PointTag {};
struct PolygonTag {};
struct PathTag {};
struct MultiPolygonTag {};
struct BoxTag {};
struct CircleTag {};
/// Meta-functions to derive the tags
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type;
template<class S> struct MultiShape { using Type = std::vector<S>; };
template<class S>
using TMultiShape =typename MultiShape<remove_cvref_t<S>>::Type;
/**
* \brief An abstraction of a box;
*/
@ -114,11 +184,16 @@ public:
inline RawPoint center() const BP2D_NOEXCEPT;
inline double area() const BP2D_NOEXCEPT {
return double(width()*height());
template<class Unit = TCompute<RawPoint>>
inline Unit area() const BP2D_NOEXCEPT {
return Unit(width())*height();
}
};
template<class S> struct PointType<_Box<S>> {
using Type = typename _Box<S>::PointType;
};
template<class RawPoint>
class _Circle {
RawPoint center_;
@ -129,7 +204,6 @@ public:
using PointType = RawPoint;
_Circle() = default;
_Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; }
@ -137,12 +211,16 @@ public:
inline double radius() const BP2D_NOEXCEPT { return radius_; }
inline void radius(double r) { radius_ = r; }
inline double area() const BP2D_NOEXCEPT {
return 2.0*Pi*radius_*radius_;
return Pi_2 * radius_ * radius_;
}
};
template<class S> struct PointType<_Circle<S>> {
using Type = typename _Circle<S>::PointType;
};
/**
* \brief An abstraction of a directed line segment with two points.
*/
@ -185,7 +263,12 @@ public:
inline Radians angleToXaxis() const;
/// The length of the segment in the measure of the coordinate system.
inline double length();
template<class Unit = TCompute<RawPoint>> inline Unit sqlength() const;
};
template<class S> struct PointType<_Segment<S>> {
using Type = typename _Circle<S>::PointType;
};
// This struct serves almost as a namespace. The only difference is that is can
@ -216,33 +299,56 @@ inline TCoord<RawPoint>& y(RawPoint& p)
return p.y();
}
template<class RawPoint>
inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
template<class RawPoint, class Unit = TCompute<RawPoint>>
inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2)
{
static_assert(always_false<RawPoint>::value,
"PointLike::distance(point, point) unimplemented!");
return 0;
auto x1 = Unit(x(p1)), y1 = Unit(y(p1)), x2 = Unit(x(p2)), y2 = Unit(y(p2));
Unit a = (x2 - x1), b = (y2 - y1);
return a * a + b * b;
}
template<class RawPoint>
inline double distance(const RawPoint& /*p1*/,
const _Segment<RawPoint>& /*s*/)
inline double distance(const RawPoint& p1, const RawPoint& p2)
{
static_assert(always_false<RawPoint>::value,
"PointLike::distance(point, segment) unimplemented!");
return 0;
return std::sqrt(squaredDistance<RawPoint, double>(p1, p2));
}
template<class RawPoint>
inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
// create perpendicular vector
template<class Pt> inline Pt perp(const Pt& p)
{
return Pt(y(p), -x(p));
}
template<class Pt, class Unit = TCompute<Pt>>
inline Unit dotperp(const Pt& a, const Pt& b)
{
return Unit(x(a)) * Unit(y(b)) - Unit(y(a)) * Unit(x(b));
}
// dot product
template<class Pt, class Unit = TCompute<Pt>>
inline Unit dot(const Pt& a, const Pt& b)
{
return Unit(x(a)) * x(b) + Unit(y(a)) * y(b);
}
// squared vector magnitude
template<class Pt, class Unit = TCompute<Pt>>
inline Unit magnsq(const Pt& p)
{
return Unit(x(p)) * x(p) + Unit(y(p)) * y(p);
}
template<class RawPoint, class Unit = TCompute<RawPoint>>
inline std::pair<Unit, bool> horizontalDistance(
const RawPoint& p, const _Segment<RawPoint>& s)
{
using Unit = TCoord<RawPoint>;
auto x = pointlike::x(p), y = pointlike::y(p);
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
namespace pl = pointlike;
auto x = Unit(pl::x(p)), y = Unit(pl::y(p));
auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first()));
auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second()));
TCoord<RawPoint> ret;
Unit ret;
if( (y < y1 && y < y2) || (y > y1 && y > y2) )
return {0, false};
@ -250,8 +356,7 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
ret = std::min( x-x1, x -x2);
else if( (y == y1 && y == y2) && (x < x1 && x < x2))
ret = -std::min(x1 - x, x2 - x);
else if(std::abs(y - y1) <= std::numeric_limits<Unit>::epsilon() &&
std::abs(y - y2) <= std::numeric_limits<Unit>::epsilon())
else if(y == y1 && y == y2)
ret = 0;
else
ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2);
@ -259,16 +364,16 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
return {ret, true};
}
template<class RawPoint>
inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
template<class RawPoint, class Unit = TCompute<RawPoint>>
inline std::pair<Unit, bool> verticalDistance(
const RawPoint& p, const _Segment<RawPoint>& s)
{
using Unit = TCoord<RawPoint>;
auto x = pointlike::x(p), y = pointlike::y(p);
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
namespace pl = pointlike;
auto x = Unit(pl::x(p)), y = Unit(pl::y(p));
auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first()));
auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second()));
TCoord<RawPoint> ret;
Unit ret;
if( (x < x1 && x < x2) || (x > x1 && x > x2) )
return {0, false};
@ -276,8 +381,7 @@ inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
ret = std::min( y-y1, y -y2);
else if( (x == x1 && x == x2) && (y < y1 && y < y2))
ret = -std::min(y1 - y, y2 - y);
else if(std::abs(x - x1) <= std::numeric_limits<Unit>::epsilon() &&
std::abs(x - x2) <= std::numeric_limits<Unit>::epsilon())
else if(x == x1 && x == x2)
ret = 0;
else
ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2);
@ -333,9 +437,10 @@ inline Radians _Segment<RawPoint>::angleToXaxis() const
}
template<class RawPoint>
inline double _Segment<RawPoint>::length()
template<class Unit>
inline Unit _Segment<RawPoint>::sqlength() const
{
return pointlike::distance(first(), second());
return pointlike::squaredDistance<RawPoint, Unit>(first(), second());
}
template<class RawPoint>
@ -346,8 +451,8 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
using Coord = TCoord<RawPoint>;
RawPoint ret = { // No rounding here, we dont know if these are int coords
static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ),
static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 )
Coord( (getX(minc) + getX(maxc)) / Coord(2) ),
Coord( (getY(minc) + getY(maxc)) / Coord(2) )
};
return ret;
@ -362,9 +467,6 @@ enum class Formats {
// used in friend declarations and can be aliased at class scope.
namespace shapelike {
template<class RawShape>
using Shapes = TMultiShape<RawShape>;
template<class RawShape>
inline RawShape create(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes)
@ -449,7 +551,7 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&)
template<class RawShape, class...Args>
inline void addVertex(RawShape& sh, const PathTag&, Args...args)
{
return sh.emplace_back(std::forward<Args>(args)...);
sh.emplace_back(std::forward<Args>(args)...);
}
template<class RawShape, class Fn>
@ -504,13 +606,8 @@ inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
"shapelike::unserialize() unimplemented!");
}
template<class RawShape>
inline double area(const RawShape& /*sh*/, const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"shapelike::area() unimplemented!");
return 0;
}
template<class Cntr, class Unit = double>
inline Unit area(const Cntr& poly, const PathTag& );
template<class RawShape>
inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
@ -556,14 +653,14 @@ inline bool touches( const TPoint<RawShape>& /*point*/,
template<class RawShape>
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/,
const PolygonTag&)
const PathTag&)
{
static_assert(always_false<RawShape>::value,
"shapelike::boundingBox(shape) unimplemented!");
}
template<class RawShapes>
inline _Box<TPoint<typename RawShapes::value_type>>
inline _Box<TPoint<RawShapes>>
boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
static_assert(always_false<RawShapes>::value,
@ -571,21 +668,10 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
}
template<class RawShape>
inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"shapelike::convexHull(shape) unimplemented!");
return RawShape();
}
inline RawShape convexHull(const RawShape& sh, const PathTag&);
template<class RawShapes>
inline typename RawShapes::value_type
convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
static_assert(always_false<RawShapes>::value,
"shapelike::convexHull(shapes) unimplemented!");
return typename RawShapes::value_type();
}
template<class RawShapes, class S = typename RawShapes::value_type>
inline S convexHull(const RawShapes& sh, const MultiPolygonTag&);
template<class RawShape>
inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
@ -745,13 +831,19 @@ inline void reserve(T& sh, size_t vertex_capacity) {
template<class RawShape, class...Args>
inline void addVertex(RawShape& sh, const PolygonTag&, Args...args)
{
return addVertex(contour(sh), PathTag(), std::forward<Args>(args)...);
addVertex(contour(sh), PathTag(), std::forward<Args>(args)...);
}
template<class RawShape, class...Args> // Tag dispatcher
inline void addVertex(RawShape& sh, Args...args)
{
return addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...);
addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...);
}
template<class RawShape>
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& poly, const PolygonTag&)
{
return boundingBox(contour(poly), PathTag());
}
template<class Box>
@ -786,7 +878,7 @@ inline _Box<TPoint<S>> boundingBox(const S& sh)
template<class Box>
inline double area(const Box& box, const BoxTag& )
{
return box.area();
return box.template area<double>();
}
template<class Circle>
@ -795,6 +887,35 @@ inline double area(const Circle& circ, const CircleTag& )
return circ.area();
}
template<class Cntr, class Unit>
inline Unit area(const Cntr& poly, const PathTag& )
{
namespace sl = shapelike;
if (sl::cend(poly) - sl::cbegin(poly) < 3) return 0.0;
Unit a = 0;
for (auto i = sl::cbegin(poly), j = std::prev(sl::cend(poly));
i < sl::cend(poly); ++i)
{
auto xj = Unit(getX(*j)), yj = Unit(getY(*j));
auto xi = Unit(getX(*i)), yi = Unit(getY(*i));
a += (xj + xi) * (yj - yi);
j = i;
}
a /= 2;
return is_clockwise<Cntr>() ? a : -a;
}
template<class S> inline double area(const S& poly, const PolygonTag& )
{
auto hls = holes(poly);
return std::accumulate(hls.begin(), hls.end(),
area(contour(poly), PathTag()),
[](double a, const TContour<S> &h){
return a + area(h, PathTag());
});
}
template<class RawShape> // Dispatching function
inline double area(const RawShape& sh)
{
@ -811,6 +932,12 @@ inline double area(const RawShapes& shapes, const MultiPolygonTag&)
});
}
template<class RawShape>
inline RawShape convexHull(const RawShape& sh, const PolygonTag&)
{
return create<RawShape>(convexHull(contour(sh), PathTag()));
}
template<class RawShape>
inline auto convexHull(const RawShape& sh)
-> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
@ -818,11 +945,91 @@ inline auto convexHull(const RawShape& sh)
return convexHull(sh, Tag<RawShape>());
}
template<class RawShape>
inline RawShape convexHull(const RawShape& sh, const PathTag&)
{
using Unit = TCompute<RawShape>;
using Point = TPoint<RawShape>;
namespace sl = shapelike;
size_t edges = sl::cend(sh) - sl::cbegin(sh);
if(edges <= 3) return {};
bool closed = false;
std::vector<Point> U, L;
U.reserve(1 + edges / 2); L.reserve(1 + edges / 2);
std::vector<Point> pts; pts.reserve(edges);
std::copy(sl::cbegin(sh), sl::cend(sh), std::back_inserter(pts));
auto fpt = pts.front(), lpt = pts.back();
if(getX(fpt) == getX(lpt) && getY(fpt) == getY(lpt)) {
closed = true; pts.pop_back();
}
std::sort(pts.begin(), pts.end(),
[](const Point& v1, const Point& v2)
{
Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
return x1 == x2 ? y1 < y2 : x1 < x2;
});
auto dir = [](const Point& p, const Point& q, const Point& r) {
return (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) -
(Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p));
};
auto ik = pts.begin();
while(ik != pts.end()) {
while(U.size() > 1 && dir(U[U.size() - 2], U.back(), *ik) <= 0)
U.pop_back();
while(L.size() > 1 && dir(L[L.size() - 2], L.back(), *ik) >= 0)
L.pop_back();
U.emplace_back(*ik);
L.emplace_back(*ik);
++ik;
}
RawShape ret; reserve(ret, U.size() + L.size());
if(is_clockwise<RawShape>()) {
for(auto it = U.begin(); it != std::prev(U.end()); ++it)
addVertex(ret, *it);
for(auto it = L.rbegin(); it != std::prev(L.rend()); ++it)
addVertex(ret, *it);
if(closed) addVertex(ret, *std::prev(L.rend()));
} else {
for(auto it = L.begin(); it != std::prev(L.end()); ++it)
addVertex(ret, *it);
for(auto it = U.rbegin(); it != std::prev(U.rend()); ++it)
addVertex(ret, *it);
if(closed) addVertex(ret, *std::prev(U.rend()));
}
return ret;
}
template<class RawShapes, class S>
inline S convexHull(const RawShapes& sh, const MultiPolygonTag&)
{
namespace sl = shapelike;
S cntr;
for(auto& poly : sh)
for(auto it = sl::cbegin(poly); it != sl::cend(poly); ++it)
addVertex(cntr, *it);
return convexHull(cntr, Tag<S>());
}
template<class TP, class TC>
inline bool isInside(const TP& point, const TC& circ,
const PointTag&, const CircleTag&)
{
return pointlike::distance(point, circ.center()) < circ.radius();
auto r = circ.radius();
return pointlike::squaredDistance(point, circ.center()) < r * r;
}
template<class TP, class TB>
@ -972,6 +1179,9 @@ template<class RawShape> inline bool isConvex(const RawShape& sh) // dispatch
using Segment = _Segment<Point>; \
using Polygons = TMultiShape<T>
namespace sl = shapelike;
namespace pl = pointlike;
}
#endif // GEOMETRY_TRAITS_HPP

View File

@ -1,26 +1,22 @@
#ifndef GEOMETRIES_NOFITPOLYGON_HPP
#define GEOMETRIES_NOFITPOLYGON_HPP
#include "geometry_traits.hpp"
#include <algorithm>
#include <functional>
#include <vector>
#include <iterator>
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
namespace __nfp {
// Do not specialize this...
template<class RawShape>
template<class RawShape, class Unit = TCompute<RawShape>>
inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
{
using Coord = TCoord<TPoint<RawShape>>;
Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2);
auto diff = y1 - y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
return y1 == y2 ? x1 < x2 : y1 < y2;
}
template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>>
@ -202,7 +198,7 @@ inline TPoint<RawShape> referenceVertex(const RawShape& sh)
* convex as well in this case.
*
*/
template<class RawShape>
template<class RawShape, class Ratio = double>
inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
const RawShape& other)
{
@ -238,12 +234,62 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
++first; ++next;
}
}
// Sort the edges by angle to X axis.
std::sort(edgelist.begin(), edgelist.end(),
[](const Edge& e1, const Edge& e2)
std::sort(edgelist.begin(), edgelist.end(),
[](const Edge& e1, const Edge& e2)
{
return e1.angleToXaxis() > e2.angleToXaxis();
Vertex ax(1, 0); // Unit vector for the X axis
// get cectors from the edges
Vertex p1 = e1.second() - e1.first();
Vertex p2 = e2.second() - e2.first();
// Quadrant mapping array. The quadrant of a vector can be determined
// from the dot product of the vector and its perpendicular pair
// with the unit vector X axis. The products will carry the values
// lcos = dot(p, ax) = l * cos(phi) and
// lsin = -dotperp(p, ax) = l * sin(phi) where
// l is the length of vector p. From the signs of these values we can
// construct an index which has the sign of lcos as MSB and the
// sign of lsin as LSB. This index can be used to retrieve the actual
// quadrant where vector p resides using the following map:
// (+ is 0, - is 1)
// cos | sin | decimal | quadrant
// + | + | 0 | 0
// + | - | 1 | 3
// - | + | 2 | 1
// - | - | 3 | 2
std::array<int, 4> quadrants {0, 3, 1, 2 };
std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
using TDots = std::array<TCompute<Vertex>, 2>;
TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) };
TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) };
// Construct the quadrant indices for p1 and p2
for(size_t i = 0; i < 2; ++i)
if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3;
else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2;
else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)];
if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant
auto lsq1 = pl::magnsq(p1); // squared magnitudes, avoid sqrt
auto lsq2 = pl::magnsq(p2); // squared magnitudes, avoid sqrt
// We will actually compare l^2 * cos^2(phi) which saturates the
// cos function. But with the quadrant info we can get the sign back
int sign = q[0] == 1 || q[0] == 2 ? -1 : 1;
// If Ratio is an actual rational type, there is no precision loss
auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0];
auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1];
return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2;
}
// If in different quadrants, compare the quadrant indices only.
return q[0] > q[1];
});
__nfp::buildPolygon(edgelist, rsh, top_nfp);
@ -253,456 +299,9 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
template<class RawShape>
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
const RawShape& cother)
const RawShape& cother)
{
// Algorithms are from the original algorithm proposed in paper:
// https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
// /////////////////////////////////////////////////////////////////////////
// Algorithm 1: Obtaining the minkowski sum
// /////////////////////////////////////////////////////////////////////////
// I guess this is not a full minkowski sum of the two input polygons by
// definition. This yields a subset that is compatible with the next 2
// algorithms.
using Result = NfpResult<RawShape>;
using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
namespace sl = shapelike;
using std::signbit;
using std::sort;
using std::vector;
using std::ref;
using std::reference_wrapper;
// TODO The original algorithms expects the stationary polygon in
// counter clockwise and the orbiter in clockwise order.
// So for preventing any further complication, I will make the input
// the way it should be, than make my way around the orientations.
// Reverse the stationary contour to counter clockwise
auto stcont = sl::contour(cstationary);
{
std::reverse(sl::begin(stcont), sl::end(stcont));
stcont.pop_back();
auto it = std::min_element(sl::begin(stcont), sl::end(stcont),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(sl::begin(stcont), it, sl::end(stcont));
sl::addVertex(stcont, sl::front(stcont));
}
RawShape stationary;
sl::contour(stationary) = stcont;
// Reverse the orbiter contour to counter clockwise
auto orbcont = sl::contour(cother);
{
std::reverse(orbcont.begin(), orbcont.end());
// Step 1: Make the orbiter reverse oriented
orbcont.pop_back();
auto it = std::min_element(orbcont.begin(), orbcont.end(),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(orbcont.begin(), it, orbcont.end());
orbcont.emplace_back(orbcont.front());
for(auto &v : orbcont) v = -v;
}
// Copy the orbiter (contour only), we will have to work on it
RawShape orbiter;
sl::contour(orbiter) = orbcont;
// An edge with additional data for marking it
struct MarkedEdge {
Edge e; Radians turn_angle = 0; bool is_turning_point = false;
MarkedEdge() = default;
MarkedEdge(const Edge& ed, Radians ta, bool tp):
e(ed), turn_angle(ta), is_turning_point(tp) {}
// debug
std::string label;
};
// Container for marked edges
using EdgeList = vector<MarkedEdge>;
EdgeList A, B;
// This is how an edge list is created from the polygons
auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) {
auto& poly = sl::contour(ppoly);
L.reserve(sl::contourVertexCount(poly));
if(dir > 0) {
auto it = poly.begin();
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != poly.end()) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
} else {
auto it = sl::rbegin(poly);
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != sl::rend(poly)) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
}
auto getTurnAngle = [](const Edge& e1, const Edge& e2) {
auto phi = e1.angleToXaxis();
auto phi_prev = e2.angleToXaxis();
auto turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= TwoPi;
if(turn_angle < -Pi) turn_angle += TwoPi;
return turn_angle;
};
auto eit = L.begin();
auto enext = std::next(eit);
eit->turn_angle = getTurnAngle(L.front().e, L.back().e);
while(enext != L.end()) {
enext->turn_angle = getTurnAngle( enext->e, eit->e);
eit->is_turning_point =
signbit(enext->turn_angle) != signbit(eit->turn_angle);
++eit; ++enext;
}
L.back().is_turning_point = signbit(L.back().turn_angle) !=
signbit(L.front().turn_angle);
};
// Step 2: Fill the edgelists
fillEdgeList(A, stationary, 1);
fillEdgeList(B, orbiter, 1);
int i = 1;
for(MarkedEdge& me : A) {
std::cout << "a" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "a"; me.label += std::to_string(i);
i++;
}
i = 1;
for(MarkedEdge& me : B) {
std::cout << "b" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "b"; me.label += std::to_string(i);
i++;
}
// A reference to a marked edge that also knows its container
struct MarkedEdgeRef {
reference_wrapper<MarkedEdge> eref;
reference_wrapper<vector<MarkedEdgeRef>> container;
Coord dir = 1; // Direction modifier
inline Radians angleX() const { return eref.get().e.angleToXaxis(); }
inline const Edge& edge() const { return eref.get().e; }
inline Edge& edge() { return eref.get().e; }
inline bool isTurningPoint() const {
return eref.get().is_turning_point;
}
inline bool isFrom(const vector<MarkedEdgeRef>& cont ) {
return &(container.get()) == &cont;
}
inline bool eq(const MarkedEdgeRef& mr) {
return &(eref.get()) == &(mr.eref.get());
}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec):
eref(er), container(ec), dir(1) {}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec,
Coord d):
eref(er), container(ec), dir(d) {}
};
using EdgeRefList = vector<MarkedEdgeRef>;
// Comparing two marked edges
auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) {
return e1.angleX() < e2.angleX();
};
EdgeRefList Aref, Bref; // We create containers for the references
Aref.reserve(A.size()); Bref.reserve(B.size());
// Fill reference container for the stationary polygon
std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) {
Aref.emplace_back( ref(me), ref(Aref) );
});
// Fill reference container for the orbiting polygon
std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) {
Bref.emplace_back( ref(me), ref(Bref) );
});
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
(const EdgeRefList& Q, const EdgeRefList& R, bool positive)
{
// Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
// Sort the containers of edge references and merge them.
// Q could be sorted only once and be reused here but we would still
// need to merge it with sorted(R).
EdgeRefList merged;
EdgeRefList S, seq;
merged.reserve(Q.size() + R.size());
merged.insert(merged.end(), R.begin(), R.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
merged.insert(merged.end(), Q.begin(), Q.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
// we don't use i, instead, q is an iterator into Q. k would be an index
// into the merged sequence but we use "it" as an iterator for that
// here we obtain references for the containers for later comparisons
const auto& Rcont = R.begin()->container.get();
const auto& Qcont = Q.begin()->container.get();
// Set the initial direction
Coord dir = 1;
// roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
if(positive) {
auto q = Q.begin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.end();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.begin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
} else {
auto q = Q.rbegin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.rend();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
S.back().dir = -1;
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.rbegin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
}
// Step 4:
// "Let starting edge r1 be in position si in sequence"
// whaaat? I guess this means the following:
auto it = S.begin();
while(!it->eq(*R.begin())) ++it;
// "Set j = 1, next = 2, direction = 1, seq1 = si"
// we don't use j, seq is expanded dynamically.
dir = 1;
auto next = std::next(R.begin()); seq.emplace_back(*it);
// Step 5:
// "If all si edges have been allocated to seqj" should mean that
// we loop until seq has equal size with S
auto send = it; //it == S.begin() ? it : std::prev(it);
while(it != S.end()) {
++it; if(it == S.end()) it = S.begin();
if(it == send) break;
if(it->isFrom(Qcont)) {
seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si"
// "If si is a turning point in Q,
// direction = - direction, next = next + direction"
if(it->isTurningPoint()) {
dir = -dir;
next += dir;
// __nfp::advance(next, R, dir > 0);
}
}
if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
// "j = j + 1, seqj = si, next = next + direction"
seq.emplace_back(*it);
next += dir;
// __nfp::advance(next, R, dir > 0);
}
}
return seq;
};
std::vector<EdgeRefList> seqlist;
seqlist.reserve(Bref.size());
EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram
// make the slope diagram of B
std::sort(Bslope.begin(), Bslope.end(), sortfn);
auto slopeit = Bslope.begin(); // search for the first turning point
while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++;
if(slopeit == Bslope.end()) {
// no turning point means convex polygon.
seqlist.emplace_back(mink(Aref, Bref, true));
} else {
int dir = 1;
auto firstturn = Bref.begin();
while(!firstturn->eq(*slopeit)) ++firstturn;
assert(firstturn != Bref.end());
EdgeRefList bgroup; bgroup.reserve(Bref.size());
bgroup.emplace_back(*slopeit);
auto b_it = std::next(firstturn);
while(b_it != firstturn) {
if(b_it == Bref.end()) b_it = Bref.begin();
while(!slopeit->eq(*b_it)) {
__nfp::advance(slopeit, Bslope, dir > 0);
}
if(!slopeit->isTurningPoint()) {
bgroup.emplace_back(*slopeit);
} else {
if(!bgroup.empty()) {
if(dir > 0) bgroup.emplace_back(*slopeit);
for(auto& me : bgroup) {
std::cout << me.eref.get().label << ", ";
}
std::cout << std::endl;
seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false));
bgroup.clear();
if(dir < 0) bgroup.emplace_back(*slopeit);
} else {
bgroup.emplace_back(*slopeit);
}
dir *= -1;
}
++b_it;
}
}
// while(it != Bref.end()) // This is step 3 and step 4 in one loop
// if(it->isTurningPoint()) {
// R = {R.last, it++};
// auto seq = mink(Q, R, orientation);
// // TODO step 6 (should be 5 shouldn't it?): linking edges from A
// // I don't get this step
// seqlist.insert(seqlist.end(), seq.begin(), seq.end());
// orientation = !orientation;
// } else ++it;
// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
// /////////////////////////////////////////////////////////////////////////
// Algorithm 2: breaking Minkowski sums into track line trips
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Algorithm 3: finding the boundary of the NFP from track line trips
// /////////////////////////////////////////////////////////////////////////
for(auto& seq : seqlist) {
std::cout << "seqlist size: " << seq.size() << std::endl;
for(auto& s : seq) {
std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", ";
}
std::cout << std::endl;
}
auto& seq = seqlist.front();
RawShape rsh;
Vertex top_nfp;
std::vector<Edge> edgelist; edgelist.reserve(seq.size());
for(auto& s : seq) {
edgelist.emplace_back(s.eref.get().e);
}
__nfp::buildPolygon(edgelist, rsh, top_nfp);
return Result(rsh, top_nfp);
return {};
}
// Specializable NFP implementation class. Specialize it if you have a faster

View File

@ -8,13 +8,10 @@
#include <algorithm>
#include <functional>
#include "geometry_traits.hpp"
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
namespace sl = shapelike;
namespace pl = pointlike;
/**
* \brief An item to be placed on a bin.
*
@ -422,13 +419,9 @@ private:
static inline bool vsort(const Vertex& v1, const Vertex& v2)
{
Coord &&x1 = getX(v1), &&x2 = getX(v2);
Coord &&y1 = getY(v1), &&y2 = getY(v2);
auto diff = y1 - y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
TCompute<Vertex> x1 = getX(v1), x2 = getX(v2);
TCompute<Vertex> y1 = getY(v1), y2 = getY(v2);
return y1 == y2 ? x1 < x2 : y1 < y2;
}
};

View File

@ -4,7 +4,8 @@
#include <tuple>
#include <functional>
#include <limits>
#include "common.hpp"
#include <libnest2d/common.hpp>
namespace libnest2d { namespace opt {
@ -60,6 +61,7 @@ enum class Method {
L_SIMPLEX,
L_SUBPLEX,
G_GENETIC,
G_PARTICLE_SWARM
//...
};

View File

@ -48,7 +48,7 @@ else()
target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt)
endif()
#target_sources( NloptOptimizer INTERFACE
#target_sources( nloptOptimizer INTERFACE
#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp
#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp
#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp
@ -57,5 +57,5 @@ endif()
target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT)
# And finally plug the NloptOptimizer into libnest2d
#target_link_libraries(libnest2d INTERFACE NloptOptimizer)
# And finally plug the nloptOptimizer into libnest2d
#target_link_libraries(libnest2d INTERFACE nloptOptimizer)

View File

@ -1,5 +0,0 @@
find_package(Armadillo REQUIRED)
add_library(OptimlibOptimizer INTERFACE)
target_include_directories(OptimlibOptimizer INTERFACE ${ARMADILLO_INCLUDE_DIRS})
target_link_libraries(OptimlibOptimizer INTERFACE ${ARMADILLO_LIBRARIES})

View File

@ -7,15 +7,15 @@
namespace libnest2d { namespace placers {
template<class T, class = T> struct Epsilon {};
template<class T, class = T> struct DefaultEpsilon {};
template<class T>
struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
struct DefaultEpsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
static const T Value = 1;
};
template<class T>
struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
struct DefaultEpsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
static const T Value = 1e-3;
};
@ -24,7 +24,7 @@ struct BLConfig {
DECLARE_MAIN_TYPES(RawShape);
Coord min_obj_distance = 0;
Coord epsilon = Epsilon<Coord>::Value;
Coord epsilon = DefaultEpsilon<Coord>::Value;
bool allow_rotations = false;
};

View File

@ -103,14 +103,14 @@ Key hash(const _Item<S>& item) {
while(deg > N) { ms++; deg -= N; }
ls += int(deg);
ret.push_back(char(ms)); ret.push_back(char(ls));
circ += seg.length();
circ += std::sqrt(seg.template sqlength<double>());
}
it = ctr.begin(); nx = std::next(it);
while(nx != ctr.end()) {
Segment seg(*it++, *nx++);
auto l = int(M * seg.length() / circ);
auto l = int(M * std::sqrt(seg.template sqlength<double>()) / circ);
int ms = 'A', ls = 'A';
while(l > N) { ms++; l -= N; }
ls += l;
@ -249,6 +249,11 @@ template<class RawShape> class EdgeCache {
std::vector<ContourCache> holes_;
double accuracy_ = 1.0;
static double length(const Edge &e)
{
return std::sqrt(e.template sqlength<double>());
}
void createCache(const RawShape& sh) {
{ // For the contour
@ -260,7 +265,7 @@ template<class RawShape> class EdgeCache {
while(next != endit) {
contour_.emap.emplace_back(*(first++), *(next++));
contour_.full_distance += contour_.emap.back().length();
contour_.full_distance += length(contour_.emap.back());
contour_.distances.emplace_back(contour_.full_distance);
}
}
@ -275,7 +280,7 @@ template<class RawShape> class EdgeCache {
while(next != endit) {
hc.emap.emplace_back(*(first++), *(next++));
hc.full_distance += hc.emap.back().length();
hc.full_distance += length(hc.emap.back());
hc.distances.emplace_back(hc.full_distance);
}

View File

@ -311,19 +311,19 @@ struct range_value<bp2d::Shapes> {
namespace libnest2d { // Now the algorithms that boost can provide...
namespace pointlike {
template<>
inline double distance(const PointImpl& p1, const PointImpl& p2 )
{
return boost::geometry::distance(p1, p2);
}
//namespace pointlike {
//template<>
//inline double distance(const PointImpl& p1, const PointImpl& p2 )
//{
// return boost::geometry::distance(p1, p2);
//}
template<>
inline double distance(const PointImpl& p, const bp2d::Segment& seg )
{
return boost::geometry::distance(p, seg);
}
}
//template<>
//inline double distance(const PointImpl& p, const bp2d::Segment& seg )
//{
// return boost::geometry::distance(p, seg);
//}
//}
namespace shapelike {
// Tell libnest2d how to make string out of a ClipperPolygon object
@ -382,16 +382,9 @@ inline bool touches( const PointImpl& point, const PolygonImpl& shape)
}
#ifndef DISABLE_BOOST_BOUNDING_BOX
template<>
inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&)
{
bp2d::Box b;
boost::geometry::envelope(sh, b);
return b;
}
template<>
inline bp2d::Box boundingBox(const PathImpl& sh, const PolygonTag&)
inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&)
{
bp2d::Box b;
boost::geometry::envelope(sh, b);
@ -410,9 +403,9 @@ inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes,
#ifndef DISABLE_BOOST_CONVEX_HULL
template<>
inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&)
inline PathImpl convexHull(const PathImpl& sh, const PathTag&)
{
PolygonImpl ret;
PathImpl ret;
boost::geometry::convex_hull(sh, ret);
return ret;
}

View File

@ -0,0 +1,268 @@
#ifndef ROTCALIPERS_HPP
#define ROTCALIPERS_HPP
#include <numeric>
#include <functional>
#include <array>
#include <cmath>
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
template<class Pt, class Unit = TCompute<Pt>> class RotatedBox {
Pt axis_;
Unit bottom_ = Unit(0), right_ = Unit(0);
public:
RotatedBox() = default;
RotatedBox(const Pt& axis, Unit b, Unit r):
axis_(axis), bottom_(b), right_(r) {}
inline long double area() const {
long double asq = pl::magnsq<Pt, long double>(axis_);
return cast<long double>(bottom_) * cast<long double>(right_) / asq;
}
inline long double width() const {
return abs(bottom_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
}
inline long double height() const {
return abs(right_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
}
inline Unit bottom_extent() const { return bottom_; }
inline Unit right_extent() const { return right_; }
inline const Pt& axis() const { return axis_; }
inline Radians angleToX() const {
double ret = std::atan2(getY(axis_), getX(axis_));
auto s = std::signbit(ret);
if(s) ret += Pi_2;
return -ret;
}
};
template <class Poly, class Pt = TPoint<Poly>, class Unit = TCompute<Pt>>
Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0))
{
Poly ret; sl::reserve(ret, sl::contourVertexCount(sh));
Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh));
auto it = sl::cbegin(sh);
auto itx = std::next(it);
if(itx != sl::cend(sh)) while (it != sl::cend(sh))
{
Pt enext = *itx - *it;
auto dp = pl::dotperp<Pt, Unit>(eprev, enext);
if(abs(dp) > eps) sl::addVertex(ret, *it);
eprev = enext;
if (++itx == sl::cend(sh)) itx = sl::cbegin(sh);
++it;
}
return ret;
}
// The area of the bounding rectangle with the axis dir and support vertices
template<class Pt, class Unit = TCompute<Pt>, class R = TCompute<Pt>>
inline R rectarea(const Pt& w, // the axis
const Pt& vb, const Pt& vr,
const Pt& vt, const Pt& vl)
{
Unit a = pl::dot<Pt, Unit>(w, vr - vl);
Unit b = pl::dot<Pt, Unit>(-pl::perp(w), vt - vb);
R m = R(a) / pl::magnsq<Pt, Unit>(w);
m = m * b;
return m;
};
template<class Pt,
class Unit = TCompute<Pt>,
class R = TCompute<Pt>,
class It = typename std::vector<Pt>::const_iterator>
inline R rectarea(const Pt& w, const std::array<It, 4>& rect)
{
return rectarea<Pt, Unit, R>(w, *rect[0], *rect[1], *rect[2], *rect[3]);
}
// This function is only applicable to counter-clockwise oriented convex
// polygons where only two points can be collinear witch each other.
template <class RawShape,
class Unit = TCompute<RawShape>,
class Ratio = TCompute<RawShape>>
RotatedBox<TPoint<RawShape>, Unit> minAreaBoundingBox(const RawShape& sh)
{
using Point = TPoint<RawShape>;
using Iterator = typename TContour<RawShape>::const_iterator;
using pointlike::dot; using pointlike::magnsq; using pointlike::perp;
// Get the first and the last vertex iterator
auto first = sl::cbegin(sh);
auto last = std::prev(sl::cend(sh));
// Check conditions and return undefined box if input is not sane.
if(last == first) return {};
if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last;
if(last - first < 2) return {};
RawShape shcpy; // empty at this point
{
Point p = *first, q = *std::next(first), r = *last;
// Determine orientation from first 3 vertex (should be consistent)
Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) -
(Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p));
if(d > 0) {
// The polygon is clockwise. A flip is needed (for now)
sl::reserve(shcpy, last - first);
auto it = last; while(it != first) sl::addVertex(shcpy, *it--);
sl::addVertex(shcpy, *first);
first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy));
}
}
// Cyclic iterator increment
auto inc = [&first, &last](Iterator& it) {
if(it == last) it = first; else ++it;
};
// Cyclic previous iterator
auto prev = [&first, &last](Iterator it) {
return it == first ? last : std::prev(it);
};
// Cyclic next iterator
auto next = [&first, &last](Iterator it) {
return it == last ? first : std::next(it);
};
// Establish initial (axis aligned) rectangle support verices by determining
// polygon extremes:
auto it = first;
Iterator minX = it, maxX = it, minY = it, maxY = it;
do { // Linear walk through the vertices and save the extreme positions
Point v = *it, d = v - *minX;
if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it;
d = v - *maxX;
if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it;
d = v - *minY;
if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it;
d = v - *maxY;
if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it;
} while(++it != std::next(last));
// Update the vertices defining the bounding rectangle. The rectangle with
// the smallest rotation is selected and the supporting vertices are
// returned in the 'rect' argument.
auto update = [&next, &inc]
(const Point& w, std::array<Iterator, 4>& rect)
{
Iterator B = rect[0], Bn = next(B);
Iterator R = rect[1], Rn = next(R);
Iterator T = rect[2], Tn = next(T);
Iterator L = rect[3], Ln = next(L);
Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L;
Point pw = perp(w);
using Pt = Point;
Unit dotwpb = dot<Pt, Unit>( w, b), dotwpr = dot<Pt, Unit>(-pw, r);
Unit dotwpt = dot<Pt, Unit>(-w, t), dotwpl = dot<Pt, Unit>( pw, l);
Unit dw = magnsq<Pt, Unit>(w);
std::array<Ratio, 4> angles;
angles[0] = (Ratio(dotwpb) / magnsq<Pt, Unit>(b)) * dotwpb;
angles[1] = (Ratio(dotwpr) / magnsq<Pt, Unit>(r)) * dotwpr;
angles[2] = (Ratio(dotwpt) / magnsq<Pt, Unit>(t)) * dotwpt;
angles[3] = (Ratio(dotwpl) / magnsq<Pt, Unit>(l)) * dotwpl;
using AngleIndex = std::pair<Ratio, size_t>;
std::vector<AngleIndex> A; A.reserve(4);
for (size_t i = 3, j = 0; j < 4; i = j++) {
if(rect[i] != rect[j] && angles[i] < dw) {
auto iv = std::make_pair(angles[i], i);
auto it = std::lower_bound(A.begin(), A.end(), iv,
[](const AngleIndex& ai,
const AngleIndex& aj)
{
return ai.first > aj.first;
});
A.insert(it, iv);
}
}
// The polygon is supposed to be a rectangle.
if(A.empty()) return false;
auto amin = A.front().first;
auto imin = A.front().second;
for(auto& a : A) if(a.first == amin) inc(rect[a.second]);
std::rotate(rect.begin(), rect.begin() + imin, rect.end());
return true;
};
Point w(1, 0);
Point w_min = w;
Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) *
(Unit(getY(*maxY)) - getY(*minY)));
std::array<Iterator, 4> rect = {minY, maxX, maxY, minX};
std::array<Iterator, 4> minrect = rect;
// An edge might be examined twice in which case the algorithm terminates.
size_t c = 0, count = last - first + 1;
std::vector<bool> edgemask(count, false);
while(c++ < count)
{
// Update the support vertices, if cannot be updated, break the cycle.
if(! update(w, rect)) break;
size_t eidx = size_t(rect[0] - first);
if(edgemask[eidx]) break;
edgemask[eidx] = true;
// get the unnormalized direction vector
w = *rect[0] - *prev(rect[0]);
// get the area of the rotated rectangle
Ratio rarea = rectarea<Point, Unit, Ratio>(w, rect);
// Update min area and the direction of the min bounding box;
if(rarea <= minarea) { w_min = w; minarea = rarea; minrect = rect; }
}
Unit a = dot<Point, Unit>(w_min, *minrect[1] - *minrect[3]);
Unit b = dot<Point, Unit>(-perp(w_min), *minrect[2] - *minrect[0]);
RotatedBox<Point, Unit> bb(w_min, a, b);
return bb;
}
template <class RawShape> Radians minAreaBoundingBoxRotation(const RawShape& sh)
{
return minAreaBoundingBox(sh).angleToX();
}
}
#endif // ROTCALIPERS_HPP

View File

@ -0,0 +1,23 @@
#include <libnest2d.h>
namespace libnest2d {
template class Nester<NfpPlacer, FirstFitSelection>;
template class Nester<BottomLeftPlacer, FirstFitSelection>;
template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
}

View File

@ -49,7 +49,12 @@ add_executable(tests_clipper_nlopt
target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} )
target_include_directories(tests_clipper_nlopt PRIVATE BEFORE
${GTEST_INCLUDE_DIRS})
target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS})
if(NOT LIBNEST2D_HEADER_ONLY)
target_link_libraries(tests_clipper_nlopt ${LIBNAME})
else()
target_link_libraries(tests_clipper_nlopt libnest2d)
endif()
add_test(libnest2d_tests tests_clipper_nlopt)

View File

@ -3,11 +3,43 @@
#include <libnest2d.h>
#include "printer_parts.h"
#include <libnest2d/geometry_traits_nfp.hpp>
//#include <libnest2d/geometry_traits_nfp.hpp>
#include "../tools/svgtools.hpp"
#include <libnest2d/utils/rotcalipers.hpp>
#include "boost/multiprecision/integer.hpp"
#include "boost/rational.hpp"
//#include "../tools/Int128.hpp"
//#include "gte/Mathematics/GteMinimumAreaBox2.h"
//#include "../tools/libnfpglue.hpp"
//#include "../tools/nfp_svgnest_glue.hpp"
namespace libnest2d {
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
using LargeInt = __int128;
#else
using LargeInt = boost::multiprecision::int128_t;
template<> struct _NumTag<LargeInt> { using Type = ScalarTag; };
#endif
template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; };
namespace nfp {
template<class S>
struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
{
NfpResult<S> operator()(const S &sh, const S &other)
{
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
}
};
}
}
std::vector<libnest2d::Item>& prusaParts() {
static std::vector<libnest2d::Item> ret;
@ -31,8 +63,8 @@ TEST(BasicFunctionality, Angles)
ASSERT_DOUBLE_EQ(rad, Pi);
ASSERT_DOUBLE_EQ(deg, 180);
ASSERT_DOUBLE_EQ(deg2, 180);
ASSERT_DOUBLE_EQ(rad, (Radians) deg);
ASSERT_DOUBLE_EQ( (Degrees) rad, deg);
ASSERT_DOUBLE_EQ(rad, Radians(deg));
ASSERT_DOUBLE_EQ( Degrees(rad), deg);
ASSERT_TRUE(rad == deg);
@ -151,12 +183,12 @@ TEST(GeometryAlgorithms, Distance) {
Segment seg(p1, p3);
ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
// ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
auto result = pointlike::horizontalDistance(p2, seg);
auto check = [](Coord val, Coord expected) {
if(std::is_floating_point<Coord>::value)
auto check = [](TCompute<Coord> val, TCompute<Coord> expected) {
if(std::is_floating_point<TCompute<Coord>>::value)
ASSERT_DOUBLE_EQ(static_cast<double>(val),
static_cast<double>(expected));
else
@ -415,7 +447,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
namespace {
using namespace libnest2d;
template<unsigned long SCALE = 1, class Bin>
template<long long SCALE = 1, class Bin>
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
@ -500,6 +532,41 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) {
}
}
TEST(GeometryAlgorithms, convexHull) {
using namespace libnest2d;
ClipperLib::Path poly = PRINTER_PART_POLYGONS[0];
auto chull = sl::convexHull(poly);
ASSERT_EQ(chull.size(), poly.size());
}
TEST(GeometryAlgorithms, NestTest) {
std::vector<Item> input = prusaParts();
PackGroup result = libnest2d::nest(input,
Box(250000000, 210000000),
[](unsigned cnt) {
std::cout
<< "parts left: " << cnt
<< std::endl;
});
ASSERT_LE(result.size(), 2);
int partsum = std::accumulate(result.begin(),
result.end(),
0,
[](int s,
const decltype(result)::value_type &bin) {
return s += bin.size();
});
ASSERT_EQ(input.size(), partsum);
}
namespace {
struct ItemPair {
@ -713,7 +780,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
auto& exportfun = exportSVG<SCALE, Box>;
auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){
auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){
testcase++;
orbiter.translate({210*SCALE, 0});
@ -820,7 +887,7 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
rect2.translate({10, 0});
rect3.translate({25, 0});
shapelike::Shapes<PolygonImpl> pile;
TMultiShape<PolygonImpl> pile;
pile.push_back(rect1.transformedShape());
pile.push_back(rect2.transformedShape());
@ -833,6 +900,126 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
ASSERT_EQ(shapelike::area(result.front()), ref.area());
}
namespace {
long double refMinAreaBox(const PolygonImpl& p) {
auto it = sl::cbegin(p), itx = std::next(it);
long double min_area = std::numeric_limits<long double>::max();
auto update_min = [&min_area, &it, &itx, &p]() {
Segment s(*it, *itx);
PolygonImpl rotated = p;
sl::rotate(rotated, -s.angleToXaxis());
auto bb = sl::boundingBox(rotated);
auto area = cast<long double>(sl::area(bb));
if(min_area > area) min_area = area;
};
while(itx != sl::cend(p)) {
update_min();
++it; ++itx;
}
it = std::prev(sl::cend(p)); itx = sl::cbegin(p);
update_min();
return min_area;
}
template<class T> struct BoostGCD {
T operator()(const T &a, const T &b) { return boost::gcd(a, b); }
};
using Unit = int64_t;
using Ratio = boost::rational<boost::multiprecision::int128_t>;// Rational<boost::multiprecision::int256_t>;
//double gteMinAreaBox(const PolygonImpl& p) {
// using GteCoord = ClipperLib::cInt;
// using GtePoint = gte::Vector2<GteCoord>;
// gte::MinimumAreaBox2<GteCoord, Ratio> mb;
// std::vector<GtePoint> points;
// points.reserve(p.Contour.size());
// for(auto& pt : p.Contour) points.emplace_back(GtePoint{GteCoord(pt.X), GteCoord(pt.Y)});
// mb(int(points.size()), points.data(), 0, nullptr, true);
// auto min_area = double(mb.GetArea());
// return min_area;
//}
}
TEST(RotatingCalipers, MinAreaBBCClk) {
// PolygonImpl poly({{-50, 30}, {-50, -50}, {50, -50}, {50, 50}, {-40, 50}});
// PolygonImpl poly({{-50, 0}, {50, 0}, {0, 100}});
auto u = [](ClipperLib::cInt n) { return n*1000000; };
PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}});
long double arearef = refMinAreaBox(poly);
long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area();
// double gtearea = gteMinAreaBox(poly);
ASSERT_LE(std::abs(area - arearef), 500e6 );
// ASSERT_LE(std::abs(gtearea - arearef), 500 );
// ASSERT_DOUBLE_EQ(gtearea, arearef);
}
TEST(RotatingCalipers, AllPrusaMinBB) {
size_t idx = 0;
long double err_epsilon = 500e6l;
for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) {
// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx];
// rinput.pop_back();
// std::reverse(rinput.begin(), rinput.end());
// PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
PolygonImpl poly(rinput);
long double arearef = refMinAreaBox(poly);
auto bb = minAreaBoundingBox<PathImpl, Unit, Ratio>(rinput);
long double area = cast<long double>(bb.area());
// double area = gteMinAreaBox(poly);
bool succ = std::abs(arearef - area) < err_epsilon;
std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
<< arearef << " actual: " << area << std::endl;
ASSERT_TRUE(succ);
}
for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) {
rinput.pop_back();
std::reverse(rinput.begin(), rinput.end());
PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
long double arearef = refMinAreaBox(poly);
auto bb = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly);
long double area = cast<long double>(bb.area());
// double area = gteMinAreaBox(poly);
bool succ = std::abs(arearef - area) < err_epsilon;
std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
<< arearef << " actual: " << area << std::endl;
ASSERT_TRUE(succ);
}
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();

View File

@ -163,6 +163,8 @@ add_library(libslic3r STATIC
MTUtils.hpp
Zipper.hpp
Zipper.cpp
MinAreaBoundingBox.hpp
MinAreaBoundingBox.cpp
miniz_extension.hpp
miniz_extension.cpp
SLA/SLABoilerPlate.hpp

View File

@ -37,6 +37,8 @@
* *
*******************************************************************************/
#ifndef SLIC3R_INT128_HPP
#define SLIC3R_INT128_HPP
// #define SLIC3R_DEBUG
// Make assert active if SLIC3R_DEBUG
@ -48,6 +50,8 @@
#endif
#include <cassert>
#include <cstdint>
#include <cmath>
#if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__)
#define HAS_INTRINSIC_128_TYPE
@ -293,3 +297,5 @@ public:
return sign_determinant_2x2(p1, q1, p2, q2) * invert;
}
};
#endif // SLIC3R_INT128_HPP

View File

@ -0,0 +1,142 @@
#include "MinAreaBoundingBox.hpp"
#include <libslic3r/ExPolygon.hpp>
#include <boost/rational.hpp>
#include <libslic3r/Int128.hpp>
#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
#include <boost/multiprecision/integer.hpp>
#endif
#include <libnest2d/geometry_traits.hpp>
#include <libnest2d/utils/rotcalipers.hpp>
namespace libnest2d {
template<> struct PointType<Slic3r::Points> { using Type = Slic3r::Point; };
template<> struct CoordType<Slic3r::Point> { using Type = coord_t; };
template<> struct ShapeTag<Slic3r::ExPolygon> { using Type = PolygonTag; };
template<> struct ShapeTag<Slic3r::Polygon> { using Type = PolygonTag; };
template<> struct ShapeTag<Slic3r::Points> { using Type = PathTag; };
template<> struct ShapeTag<Slic3r::Point> { using Type = PointTag; };
template<> struct ContourType<Slic3r::ExPolygon> { using Type = Slic3r::Points; };
template<> struct ContourType<Slic3r::Polygon> { using Type = Slic3r::Points; };
namespace pointlike {
template<> inline coord_t x(const Slic3r::Point& p) { return p.x(); }
template<> inline coord_t y(const Slic3r::Point& p) { return p.y(); }
template<> inline coord_t& x(Slic3r::Point& p) { return p.x(); }
template<> inline coord_t& y(Slic3r::Point& p) { return p.y(); }
} // pointlike
namespace shapelike {
template<> inline Slic3r::Points& contour(Slic3r::ExPolygon& sh) { return sh.contour.points; }
template<> inline const Slic3r::Points& contour(const Slic3r::ExPolygon& sh) { return sh.contour.points; }
template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.points; }
template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; }
template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();}
template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.begin(); }
template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();}
template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); }
template<> inline Slic3r::ExPolygon create<Slic3r::ExPolygon>(Slic3r::Points&& contour)
{
Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour);
return expoly;
}
template<> inline Slic3r::Polygon create<Slic3r::Polygon>(Slic3r::Points&& contour)
{
Slic3r::Polygon poly; poly.points.swap(contour);
return poly;
}
} // shapelike
} // libnest2d
namespace Slic3r {
// Used as compute type.
using Unit = int64_t;
#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
using Rational = boost::rational<boost::multiprecision::int128_t>;
#else
using Rational = boost::rational<__int128>;
#endif
MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc)
{
const Polygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p);
libnest2d::RotatedBox<Point, Unit> box =
libnest2d::minAreaBoundingBox<Polygon, Unit, Rational>(chull);
m_right = box.right_extent();
m_bottom = box.bottom_extent();
m_axis = box.axis();
}
MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc)
{
const ExPolygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p);
libnest2d::RotatedBox<Point, Unit> box =
libnest2d::minAreaBoundingBox<ExPolygon, Unit, Rational>(chull);
m_right = box.right_extent();
m_bottom = box.bottom_extent();
m_axis = box.axis();
}
MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc)
{
const Points& chull = pc == pcConvex ? pts : libnest2d::sl::convexHull(pts);
libnest2d::RotatedBox<Point, Unit> box =
libnest2d::minAreaBoundingBox<Points, Unit, Rational>(chull);
m_right = box.right_extent();
m_bottom = box.bottom_extent();
m_axis = box.axis();
}
double MinAreaBoundigBox::angle_to_X() const
{
double ret = std::atan2(m_axis.y(), m_axis.x());
auto s = std::signbit(ret);
if(s) ret += 2 * PI;
return -ret;
}
long double MinAreaBoundigBox::width() const
{
return std::abs(m_bottom) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis));
}
long double MinAreaBoundigBox::height() const
{
return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis));
}
long double MinAreaBoundigBox::area() const
{
long double asq = libnest2d::pl::magnsq<Point, long double>(m_axis);
return m_bottom * m_right / asq;
}
void remove_collinear_points(Polygon &p)
{
p = libnest2d::removeCollinearPoints<Polygon>(p, Unit(0));
}
void remove_collinear_points(ExPolygon &p)
{
p = libnest2d::removeCollinearPoints<ExPolygon>(p, Unit(0));
}
}

View File

@ -0,0 +1,59 @@
#ifndef MINAREABOUNDINGBOX_HPP
#define MINAREABOUNDINGBOX_HPP
#include "libslic3r/Point.hpp"
namespace Slic3r {
class Polygon;
class ExPolygon;
void remove_collinear_points(Polygon& p);
void remove_collinear_points(ExPolygon& p);
/// A class that holds a rotated bounding box. If instantiated with a polygon
/// type it will hold the minimum area bounding box for the given polygon.
/// If the input polygon is convex, the complexity is linear to the number of
/// points. Otherwise a convex hull of O(n*log(n)) has to be performed.
class MinAreaBoundigBox {
Point m_axis;
long double m_bottom = 0.0l, m_right = 0.0l;
public:
// Polygons can be convex or simple (convex or concave with possible holes)
enum PolygonLevel {
pcConvex, pcSimple
};
// Constructors with various types of geometry data used in Slic3r.
// If the convexity is known apriory, pcConvex can be used to skip
// convex hull calculation. It is very important that the input polygons
// do NOT have any collinear points (except for the first and the last
// vertex being the same -- meaning a closed polygon for boost)
// To make sure this constraint is satisfied, you can call
// remove_collinear_points on the input polygon before handing over here)
explicit MinAreaBoundigBox(const Polygon&, PolygonLevel = pcSimple);
explicit MinAreaBoundigBox(const ExPolygon&, PolygonLevel = pcSimple);
explicit MinAreaBoundigBox(const Points&, PolygonLevel = pcSimple);
// Returns the angle in radians needed for the box to be aligned with the
// X axis. Rotate the polygon by this angle and it will be aligned.
double angle_to_X() const;
// The box width
long double width() const;
// The box height
long double height() const;
// The box area
long double area() const;
// The axis of the rotated box. If the angle_to_X is not sufficiently
// precise, use this unnormalized direction vector.
const Point& axis() const { return m_axis; }
};
}
#endif // MINAREABOUNDINGBOX_HPP

View File

@ -9,6 +9,31 @@
#include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/rational.hpp>
namespace libnest2d {
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
using LargeInt = __int128;
#else
using LargeInt = boost::multiprecision::int128_t;
template<> struct _NumTag<LargeInt> { using Type = ScalarTag; };
#endif
template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; };
namespace nfp {
template<class S>
struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
{
NfpResult<S> operator()(const S &sh, const S &other)
{
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
}
};
}
}
namespace Slic3r {
@ -130,7 +155,7 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) {
// at the same time, it has to provide reasonable results.
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const PointImpl& bincenter,
const shapelike::Shapes<PolygonImpl>& merged_pile,
const TMultiShape<PolygonImpl>& merged_pile,
const Box& pilebb,
const ItemGroup& items,
const Item &item,
@ -301,7 +326,7 @@ protected:
using Packer = Nester<Placer, Selector>;
using PConfig = typename Packer::PlacementConfig;
using Distance = TCoord<PointImpl>;
using Pile = sl::Shapes<PolygonImpl>;
using Pile = TMultiShape<PolygonImpl>;
Packer m_pck;
PConfig m_pconf; // Placement configuration
@ -589,7 +614,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo&
// Invalid geometries would throw exceptions when arranging
if(item.vertexCount() > 3) {
item.rotation(float(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()))),
item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()));
item.translation({
ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR),
ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR)

View File

@ -37,7 +37,12 @@
#include "libslic3r/SLA/SLARotfinder.hpp"
#include "libslic3r/Utils.hpp"
#include "libnest2d/optimizers/nlopt/genetic.hpp"
//#include "libslic3r/ClipperUtils.hpp"
// #include "libnest2d/optimizers/nlopt/genetic.hpp"
// #include "libnest2d/backends/clipper/geometries.hpp"
// #include "libnest2d/utils/rotcalipers.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
@ -2261,46 +2266,18 @@ void Plater::priv::sla_optimize_rotation() {
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
double mindist = 6.0; // FIXME
double offs = mindist / 2.0 - EPSILON;
if(rotoptimizing) // wasn't canceled
for(ModelInstance * oi : o->instances) {
oi->set_rotation({r[X], r[Y], r[Z]});
auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix());
namespace opt = libnest2d::opt;
opt::StopCriteria stopcr;
stopcr.relative_score_difference = 0.01;
stopcr.max_iterations = 10000;
stopcr.stop_score = 0.0;
opt::GeneticOptimizer solver(stopcr);
Polygon pbed(bed);
auto bin = pbed.bounding_box();
double binw = bin.size()(X) * SCALING_FACTOR - offs;
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
auto result = solver.optimize_min([&trchull, binw, binh](double rot){
auto chull = trchull;
chull.rotate(rot);
auto bb = chull.bounding_box();
double bbw = bb.size()(X) * SCALING_FACTOR;
double bbh = bb.size()(Y) * SCALING_FACTOR;
auto wdiff = bbw - binw;
auto hdiff = bbh - binh;
double diff = 0;
if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff;
if(wdiff > 0) diff += wdiff;
if(hdiff > 0) diff += hdiff;
return diff;
}, opt::initvals(0.0), opt::bound(-PI/2, PI/2));
double r = std::get<0>(result.optimum);
Polygon trchull = o->convex_hull_2d(oi->get_transformation().get_matrix());
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
double r = rotbb.angle_to_X();
// The box should be landscape
if(rotbb.width() < rotbb.height()) r += PI / 2;
Vec3d rt = oi->get_rotation(); rt(Z) += r;
oi->set_rotation(rt);
}