Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
222c6a2df5 | ||
|
|
70bc02467b | ||
|
|
4ffba13210 | ||
|
|
a1ad2b4425 | ||
|
|
6c6e8be43d |
@@ -259,6 +259,7 @@ jobs:
|
||||
# Thanks to RaySajuuk, it's working now
|
||||
- name: Sign app and notary
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/belt-printer' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'macOS' && inputs.macos-combine-only
|
||||
timeout-minutes: 30
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
|
||||
|
||||
@@ -823,7 +823,9 @@ if(SLIC3R_STATIC)
|
||||
set(TBB_STATIC 1)
|
||||
endif()
|
||||
set(TBB_DEBUG 1)
|
||||
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release "")
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" OR MSVC)
|
||||
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release "")
|
||||
endif()
|
||||
find_package(TBB REQUIRED)
|
||||
# include_directories(SYSTEM ${TBB_INCLUDE_DIRS})
|
||||
# add_definitions(${TBB_DEFINITIONS})
|
||||
|
||||
@@ -15,7 +15,6 @@ add_subdirectory(stb_dxt) # Header-only STB DXT compression library
|
||||
# Static libraries
|
||||
add_subdirectory(Shiny)
|
||||
add_subdirectory(admesh)
|
||||
add_subdirectory(clipper)
|
||||
add_subdirectory(clipper2)
|
||||
add_subdirectory(expat)
|
||||
add_subdirectory(glu-libtess)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(clipper)
|
||||
|
||||
add_library(clipper STATIC
|
||||
# We are using ClipperLib compiled as part of the libslic3r project using Slic3r::Point as its base type.
|
||||
# clipper.cpp
|
||||
# clipper.hpp
|
||||
clipper_z.cpp
|
||||
clipper_z.hpp
|
||||
)
|
||||
|
||||
target_include_directories(clipper SYSTEM
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(clipper
|
||||
PUBLIC Eigen3::Eigen
|
||||
PRIVATE TBB::tbb TBB::tbbmalloc
|
||||
)
|
||||
@@ -1,606 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Author : Angus Johnson *
|
||||
* Version : 6.4.2 *
|
||||
* Date : 27 February 2017 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2017 *
|
||||
* *
|
||||
* License: *
|
||||
* Use, modification & distribution is subject to Boost Software License Ver 1. *
|
||||
* http://www.boost.org/LICENSE_1_0.txt *
|
||||
* *
|
||||
* Attributions: *
|
||||
* The code in this library is an extension of Bala Vatti's clipping algorithm: *
|
||||
* "A generic solution to polygon clipping" *
|
||||
* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
|
||||
* http://portal.acm.org/citation.cfm?id=129906 *
|
||||
* *
|
||||
* Computer graphics and geometric modeling: implementation and algorithms *
|
||||
* By Max K. Agoston *
|
||||
* Springer; 1 edition (January 4, 2005) *
|
||||
* http://books.google.com/books?q=vatti+clipping+agoston *
|
||||
* *
|
||||
* See also: *
|
||||
* "Polygon Offsetting by Computing Winding Numbers" *
|
||||
* Paper no. DETC2005-85513 pp. 565-575 *
|
||||
* ASME 2005 International Design Engineering Technical Conferences *
|
||||
* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
|
||||
* September 24-28, 2005 , Long Beach, California, USA *
|
||||
* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef clipper_hpp
|
||||
#define clipper_hpp
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <functional>
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include <oneapi/tbb/scalable_allocator.h>
|
||||
|
||||
#define CLIPPER_VERSION "6.2.6"
|
||||
|
||||
//CLIPPERLIB_USE_XYZ: adds a Z member to IntPoint. Adds a minor cost to perfomance.
|
||||
//#define CLIPPERLIB_USE_XYZ
|
||||
|
||||
//use_lines: Enables line clipping. Adds a very minor cost to performance.
|
||||
#define use_lines
|
||||
|
||||
//use_deprecated: Enables temporary support for the obsolete functions
|
||||
//#define use_deprecated
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <ostream>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
|
||||
#ifdef CLIPPERLIB_NAMESPACE_PREFIX
|
||||
namespace CLIPPERLIB_NAMESPACE_PREFIX {
|
||||
#endif // CLIPPERLIB_NAMESPACE_PREFIX
|
||||
|
||||
#ifdef CLIPPERLIB_USE_XYZ
|
||||
namespace ClipperLib_Z {
|
||||
#else
|
||||
namespace ClipperLib {
|
||||
#endif
|
||||
|
||||
enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
|
||||
enum PolyType { ptSubject, ptClip };
|
||||
//By far the most widely used winding rules for polygon filling are
|
||||
//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
|
||||
//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
|
||||
//see http://glprogramming.com/red/chapter11.html
|
||||
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
|
||||
|
||||
// If defined, Clipper will work with 32bit signed int coordinates to reduce memory
|
||||
// consumption and to speed up exact orientation predicate calculation.
|
||||
// In that case, coordinates and their differences (vectors of the coordinates) have to fit int32_t.
|
||||
// #define CLIPPERLIB_INT32
|
||||
|
||||
// Point coordinate type
|
||||
#ifdef CLIPPERLIB_INT32
|
||||
// Coordinates and their differences (vectors of the coordinates) have to fit int32_t.
|
||||
using cInt = int32_t;
|
||||
using CrossProductType = int64_t;
|
||||
#else
|
||||
using cInt = int64_t;
|
||||
using CrossProductType = double;
|
||||
// Maximum cInt value to allow a cross product calculation using 32bit expressions.
|
||||
static constexpr cInt const loRange = 0x3FFFFFFF; // 0x3FFFFFFF = 1 073 741 823
|
||||
// Maximum allowed cInt value.
|
||||
static constexpr cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
|
||||
#endif // CLIPPERLIB_INT32
|
||||
|
||||
#ifdef CLIPPERLIB_INTPOINT_TYPE
|
||||
using IntPoint = CLIPPERLIB_INTPOINT_TYPE;
|
||||
#else // CLIPPERLIB_INTPOINT_TYPE
|
||||
using IntPoint = Eigen::Matrix<cInt,
|
||||
#ifdef CLIPPERLIB_USE_XYZ
|
||||
3
|
||||
#else // CLIPPERLIB_USE_XYZ
|
||||
2
|
||||
#endif // CLIPPERLIB_USE_XYZ
|
||||
, 1, Eigen::DontAlign>;
|
||||
#endif // CLIPPERLIB_INTPOINT_TYPE
|
||||
|
||||
using DoublePoint = Eigen::Matrix<double, 2, 1, Eigen::DontAlign>;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<typename BaseType>
|
||||
using Allocator = tbb::scalable_allocator<BaseType>;
|
||||
//using Allocator = std::allocator<BaseType>;
|
||||
using Path = std::vector<IntPoint, Allocator<IntPoint>>;
|
||||
using Paths = std::vector<Path, Allocator<Path>>;
|
||||
|
||||
inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;}
|
||||
inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;}
|
||||
|
||||
std::ostream& operator <<(std::ostream &s, const IntPoint &p);
|
||||
std::ostream& operator <<(std::ostream &s, const Path &p);
|
||||
std::ostream& operator <<(std::ostream &s, const Paths &p);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#ifdef CLIPPERLIB_USE_XYZ
|
||||
typedef std::function<void(const IntPoint& e1bot, const IntPoint& e1top, const IntPoint& e2bot, const IntPoint& e2top, IntPoint& pt)> ZFillCallback;
|
||||
#endif
|
||||
|
||||
enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
|
||||
enum JoinType {jtSquare, jtRound, jtMiter};
|
||||
enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
|
||||
|
||||
class PolyNode;
|
||||
typedef std::vector<PolyNode*, Allocator<PolyNode*>> PolyNodes;
|
||||
|
||||
class PolyNode
|
||||
{
|
||||
public:
|
||||
PolyNode() : Parent(0), Index(0), m_IsOpen(false) {}
|
||||
virtual ~PolyNode(){};
|
||||
Path Contour;
|
||||
PolyNodes Childs;
|
||||
PolyNode* Parent;
|
||||
// Traversal of the polygon tree in a depth first fashion.
|
||||
PolyNode* GetNext() const { return Childs.empty() ? GetNextSiblingUp() : Childs.front(); }
|
||||
bool IsHole() const;
|
||||
bool IsOpen() const { return m_IsOpen; }
|
||||
int ChildCount() const { return (int)Childs.size(); }
|
||||
private:
|
||||
unsigned Index; //node index in Parent.Childs
|
||||
bool m_IsOpen;
|
||||
JoinType m_jointype;
|
||||
EndType m_endtype;
|
||||
PolyNode* GetNextSiblingUp() const { return Parent ? ((Index == Parent->Childs.size() - 1) ? Parent->GetNextSiblingUp() : Parent->Childs[Index + 1]) : nullptr; }
|
||||
void AddChild(PolyNode& child);
|
||||
friend class Clipper; //to access Index
|
||||
friend class ClipperOffset;
|
||||
friend class PolyTree; //to implement the PolyTree::move operator
|
||||
};
|
||||
|
||||
class PolyTree: public PolyNode
|
||||
{
|
||||
public:
|
||||
PolyTree() {}
|
||||
PolyTree(PolyTree &&src) { *this = std::move(src); }
|
||||
virtual ~PolyTree(){Clear();};
|
||||
PolyTree& operator=(PolyTree &&src) {
|
||||
AllNodes = std::move(src.AllNodes);
|
||||
Contour = std::move(src.Contour);
|
||||
Childs = std::move(src.Childs);
|
||||
Parent = nullptr;
|
||||
Index = src.Index;
|
||||
m_IsOpen = src.m_IsOpen;
|
||||
m_jointype = src.m_jointype;
|
||||
m_endtype = src.m_endtype;
|
||||
for (size_t i = 0; i < Childs.size(); ++ i)
|
||||
Childs[i]->Parent = this;
|
||||
return *this;
|
||||
}
|
||||
PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); }
|
||||
void Clear() { AllNodes.clear(); Childs.clear(); }
|
||||
int Total() const;
|
||||
void RemoveOutermostPolygon();
|
||||
private:
|
||||
PolyTree(const PolyTree &src) = delete;
|
||||
PolyTree& operator=(const PolyTree &src) = delete;
|
||||
std::vector<PolyNode, Allocator<PolyNode>> AllNodes;
|
||||
friend class Clipper; //to access AllNodes
|
||||
};
|
||||
|
||||
double Area(const Path &poly);
|
||||
inline bool Orientation(const Path &poly) { return Area(poly) >= 0; }
|
||||
int PointInPolygon(const IntPoint &pt, const Path &path);
|
||||
|
||||
// Union with "strictly simple" fix enabled.
|
||||
Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftNonZero, bool strictly_simple = true);
|
||||
|
||||
void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415);
|
||||
void CleanPolygon(Path& poly, double distance = 1.415);
|
||||
void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415);
|
||||
void CleanPolygons(Paths& polys, double distance = 1.415);
|
||||
|
||||
void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
|
||||
void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
|
||||
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
|
||||
|
||||
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
|
||||
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths);
|
||||
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
|
||||
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);
|
||||
|
||||
void ReversePath(Path& p);
|
||||
void ReversePaths(Paths& p);
|
||||
|
||||
struct IntRect { cInt left; cInt top; cInt right; cInt bottom; };
|
||||
|
||||
//enums that are used internally ...
|
||||
enum EdgeSide { esLeft = 1, esRight = 2};
|
||||
|
||||
// namespace Internal {
|
||||
//forward declarations (for stuff used internally) ...
|
||||
struct TEdge {
|
||||
// Bottom point of this edge (with minimum Y).
|
||||
IntPoint Bot;
|
||||
// Current position.
|
||||
IntPoint Curr;
|
||||
// Top point of this edge (with maximum Y).
|
||||
IntPoint Top;
|
||||
// Slope (dx/dy). For horiontal edges, the slope is set to HORIZONTAL (-1.0E+40).
|
||||
double Dx;
|
||||
PolyType PolyTyp;
|
||||
EdgeSide Side;
|
||||
// Winding number delta. 1 or -1 depending on winding direction, 0 for open paths and flat closed paths.
|
||||
int WindDelta;
|
||||
int WindCnt;
|
||||
int WindCnt2; //winding count of the opposite polytype
|
||||
int OutIdx;
|
||||
// Next edge in the input path.
|
||||
TEdge *Next;
|
||||
// Previous edge in the input path.
|
||||
TEdge *Prev;
|
||||
// Next edge in the Local Minima List chain.
|
||||
TEdge *NextInLML;
|
||||
TEdge *NextInAEL;
|
||||
TEdge *PrevInAEL;
|
||||
TEdge *NextInSEL;
|
||||
TEdge *PrevInSEL;
|
||||
};
|
||||
|
||||
struct IntersectNode {
|
||||
IntersectNode(TEdge *Edge1, TEdge *Edge2, IntPoint Pt) :
|
||||
Edge1(Edge1), Edge2(Edge2), Pt(Pt) {}
|
||||
TEdge *Edge1;
|
||||
TEdge *Edge2;
|
||||
IntPoint Pt;
|
||||
};
|
||||
|
||||
struct LocalMinimum {
|
||||
cInt Y;
|
||||
TEdge *LeftBound;
|
||||
TEdge *RightBound;
|
||||
};
|
||||
|
||||
// Point of an output polygon.
|
||||
// 36B on 64bit system without CLIPPERLIB_USE_XYZ.
|
||||
struct OutPt {
|
||||
// 4B
|
||||
int Idx;
|
||||
// 16B without CLIPPERLIB_USE_XYZ / 24B with CLIPPERLIB_USE_XYZ
|
||||
IntPoint Pt;
|
||||
// 4B on 32bit system, 8B on 64bit system
|
||||
OutPt *Next;
|
||||
// 4B on 32bit system, 8B on 64bit system
|
||||
OutPt *Prev;
|
||||
};
|
||||
|
||||
using OutPts = std::vector<OutPt, Allocator<OutPt>>;
|
||||
|
||||
// Output polygon.
|
||||
struct OutRec {
|
||||
int Idx;
|
||||
bool IsHole;
|
||||
bool IsOpen;
|
||||
//The 'FirstLeft' field points to another OutRec that contains or is the
|
||||
//'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is
|
||||
//parsed left from the current edge (owning OutRec) until the owner OutRec
|
||||
//is found. This field simplifies sorting the polygons into a tree structure
|
||||
//which reflects the parent/child relationships of all polygons.
|
||||
//This field should be renamed Parent, and will be later.
|
||||
OutRec* FirstLeft;
|
||||
// Used only by void Clipper::BuildResult2(PolyTree& polytree)
|
||||
PolyNode* PolyNd;
|
||||
// Linked list of output points, dynamically allocated.
|
||||
OutPt* Pts;
|
||||
OutPt* BottomPt;
|
||||
};
|
||||
|
||||
struct Join {
|
||||
Join(OutPt *OutPt1, OutPt *OutPt2, IntPoint OffPt) :
|
||||
OutPt1(OutPt1), OutPt2(OutPt2), OffPt(OffPt) {}
|
||||
OutPt *OutPt1;
|
||||
OutPt *OutPt2;
|
||||
IntPoint OffPt;
|
||||
};
|
||||
// }; // namespace Internal
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//ClipperBase is the ancestor to the Clipper class. It should not be
|
||||
//instantiated directly. This class simply abstracts the conversion of sets of
|
||||
//polygon coordinates into edge objects that are stored in a LocalMinima list.
|
||||
class ClipperBase
|
||||
{
|
||||
public:
|
||||
ClipperBase() :
|
||||
#ifndef CLIPPERLIB_INT32
|
||||
m_UseFullRange(false),
|
||||
#endif // CLIPPERLIB_INT32
|
||||
m_HasOpenPaths(false) {}
|
||||
~ClipperBase() { Clear(); }
|
||||
bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
|
||||
|
||||
template<typename PathsProvider>
|
||||
bool AddPaths(PathsProvider &&paths_provider, PolyType PolyTyp, bool Closed)
|
||||
{
|
||||
size_t num_paths = paths_provider.size();
|
||||
if (num_paths == 0)
|
||||
return false;
|
||||
if (num_paths == 1)
|
||||
return AddPath(*paths_provider.begin(), PolyTyp, Closed);
|
||||
|
||||
std::vector<int, Allocator<int>> num_edges(num_paths, 0);
|
||||
int num_edges_total = 0;
|
||||
size_t i = 0;
|
||||
for (const Path &pg : paths_provider) {
|
||||
// Remove duplicate end point from a closed input path.
|
||||
// Remove duplicate points from the end of the input path.
|
||||
int highI = (int)pg.size() -1;
|
||||
if (Closed)
|
||||
while (highI > 0 && (pg[highI] == pg[0]))
|
||||
--highI;
|
||||
while (highI > 0 && (pg[highI] == pg[highI -1]))
|
||||
--highI;
|
||||
if ((Closed && highI < 2) || (!Closed && highI < 1))
|
||||
highI = -1;
|
||||
num_edges[i ++] = highI + 1;
|
||||
num_edges_total += highI + 1;
|
||||
}
|
||||
if (num_edges_total == 0)
|
||||
return false;
|
||||
|
||||
// Allocate a new edge array.
|
||||
std::vector<TEdge, Allocator<TEdge>> edges(num_edges_total);
|
||||
// Fill in the edge array.
|
||||
bool result = false;
|
||||
TEdge *p_edge = edges.data();
|
||||
i = 0;
|
||||
for (const Path &pg : paths_provider) {
|
||||
if (num_edges[i] && !pg.empty()) {
|
||||
bool res = AddPathInternal(pg, num_edges[i] - 1, PolyTyp, Closed, p_edge);
|
||||
if (res) {
|
||||
p_edge += num_edges[i];
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
++ i;
|
||||
}
|
||||
if (result)
|
||||
// At least some edges were generated. Remember the edge array.
|
||||
m_edges.emplace_back(std::move(edges));
|
||||
return result;
|
||||
}
|
||||
|
||||
void Clear();
|
||||
IntRect GetBounds();
|
||||
// By default, when three or more vertices are collinear in input polygons (subject or clip), the Clipper object removes the 'inner' vertices before clipping.
|
||||
// When enabled the PreserveCollinear property prevents this default behavior to allow these inner vertices to appear in the solution.
|
||||
bool PreserveCollinear() const {return m_PreserveCollinear;};
|
||||
void PreserveCollinear(bool value) {m_PreserveCollinear = value;};
|
||||
protected:
|
||||
bool AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, bool Closed, TEdge* edges);
|
||||
TEdge* AddBoundsToLML(TEdge *e, bool IsClosed);
|
||||
void Reset();
|
||||
TEdge* ProcessBound(TEdge* E, bool IsClockwise);
|
||||
TEdge* DescendToMin(TEdge *&E);
|
||||
void AscendToMax(TEdge *&E, bool Appending, bool IsClosed);
|
||||
|
||||
// Local minima (Y, left edge, right edge) sorted by ascending Y.
|
||||
std::vector<LocalMinimum, Allocator<LocalMinimum>> m_MinimaList;
|
||||
|
||||
#ifdef CLIPPERLIB_INT32
|
||||
static constexpr const bool m_UseFullRange = false;
|
||||
#else // CLIPPERLIB_INT32
|
||||
// True if the input polygons have abs values higher than loRange, but lower than hiRange.
|
||||
// False if the input polygons have abs values lower or equal to loRange.
|
||||
bool m_UseFullRange;
|
||||
#endif // CLIPPERLIB_INT32
|
||||
|
||||
// A vector of edges per each input path.
|
||||
using Edges = std::vector<TEdge, Allocator<TEdge>>;
|
||||
std::vector<Edges, Allocator<Edges>> m_edges;
|
||||
// Don't remove intermediate vertices of a collinear sequence of points.
|
||||
bool m_PreserveCollinear;
|
||||
// Is any of the paths inserted by AddPath() or AddPaths() open?
|
||||
bool m_HasOpenPaths;
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Clipper : public ClipperBase
|
||||
{
|
||||
public:
|
||||
Clipper(int initOptions = 0);
|
||||
~Clipper() { Clear(); }
|
||||
void Clear() { ClipperBase::Clear(); DisposeAllOutRecs(); }
|
||||
bool Execute(ClipType clipType,
|
||||
Paths &solution,
|
||||
PolyFillType fillType = pftEvenOdd)
|
||||
{ return Execute(clipType, solution, fillType, fillType); }
|
||||
bool Execute(ClipType clipType,
|
||||
Paths &solution,
|
||||
PolyFillType subjFillType,
|
||||
PolyFillType clipFillType);
|
||||
bool Execute(ClipType clipType,
|
||||
PolyTree &polytree,
|
||||
PolyFillType fillType = pftEvenOdd)
|
||||
{ return Execute(clipType, polytree, fillType, fillType); }
|
||||
bool Execute(ClipType clipType,
|
||||
PolyTree &polytree,
|
||||
PolyFillType subjFillType,
|
||||
PolyFillType clipFillType);
|
||||
bool ReverseSolution() const { return m_ReverseOutput; };
|
||||
void ReverseSolution(bool value) {m_ReverseOutput = value;};
|
||||
bool StrictlySimple() const {return m_StrictSimple;};
|
||||
void StrictlySimple(bool value) {m_StrictSimple = value;};
|
||||
//set the callback function for z value filling on intersections (otherwise Z is 0)
|
||||
#ifdef CLIPPERLIB_USE_XYZ
|
||||
void ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; }
|
||||
#endif
|
||||
protected:
|
||||
void Reset();
|
||||
virtual bool ExecuteInternal();
|
||||
private:
|
||||
|
||||
// Output polygons.
|
||||
std::deque<OutRec, Allocator<OutRec>> m_PolyOuts;
|
||||
// Output points, allocated by a continuous sets of m_OutPtsChunkSize.
|
||||
static constexpr const size_t m_OutPtsChunkSize = 32;
|
||||
std::deque<std::array<OutPt, m_OutPtsChunkSize>, Allocator<std::array<OutPt, m_OutPtsChunkSize>>> m_OutPts;
|
||||
// List of free output points, to be used before taking a point from m_OutPts or allocating a new chunk.
|
||||
OutPt *m_OutPtsFree;
|
||||
size_t m_OutPtsChunkLast;
|
||||
|
||||
std::vector<Join, Allocator<Join>> m_Joins;
|
||||
std::vector<Join, Allocator<Join>> m_GhostJoins;
|
||||
std::vector<IntersectNode, Allocator<IntersectNode>> m_IntersectList;
|
||||
ClipType m_ClipType;
|
||||
// A priority queue (a binary heap) of Y coordinates.
|
||||
using cInts = std::vector<cInt, Allocator<cInt>>;
|
||||
std::priority_queue<cInt, cInts> m_Scanbeam;
|
||||
// Maxima are collected by ProcessEdgesAtTopOfScanbeam(), consumed by ProcessHorizontal().
|
||||
cInts m_Maxima;
|
||||
TEdge *m_ActiveEdges;
|
||||
TEdge *m_SortedEdges;
|
||||
PolyFillType m_ClipFillType;
|
||||
PolyFillType m_SubjFillType;
|
||||
bool m_ReverseOutput;
|
||||
// Does the result go to a PolyTree or Paths?
|
||||
bool m_UsingPolyTree;
|
||||
bool m_StrictSimple;
|
||||
#ifdef CLIPPERLIB_USE_XYZ
|
||||
ZFillCallback m_ZFill; //custom callback
|
||||
#endif
|
||||
void SetWindingCount(TEdge& edge) const;
|
||||
bool IsEvenOddFillType(const TEdge& edge) const
|
||||
{ return (edge.PolyTyp == ptSubject) ? m_SubjFillType == pftEvenOdd : m_ClipFillType == pftEvenOdd; }
|
||||
bool IsEvenOddAltFillType(const TEdge& edge) const
|
||||
{ return (edge.PolyTyp == ptSubject) ? m_ClipFillType == pftEvenOdd : m_SubjFillType == pftEvenOdd; }
|
||||
void InsertLocalMinimaIntoAEL(const cInt botY);
|
||||
void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge);
|
||||
void AddEdgeToSEL(TEdge *edge);
|
||||
void CopyAELToSEL();
|
||||
void DeleteFromSEL(TEdge *e);
|
||||
void DeleteFromAEL(TEdge *e);
|
||||
void UpdateEdgeIntoAEL(TEdge *&e);
|
||||
void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
|
||||
bool IsContributing(const TEdge& edge) const;
|
||||
bool IsTopHorz(const cInt XPos);
|
||||
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
|
||||
void DoMaxima(TEdge *e);
|
||||
void ProcessHorizontals();
|
||||
void ProcessHorizontal(TEdge *horzEdge);
|
||||
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutRec* GetOutRec(int idx);
|
||||
void AppendPolygon(TEdge *e1, TEdge *e2);
|
||||
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
|
||||
OutRec* CreateOutRec();
|
||||
OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
|
||||
OutPt* GetLastOutPt(TEdge *e);
|
||||
OutPt* AllocateOutPt();
|
||||
OutPt* DupOutPt(OutPt* outPt, bool InsertAfter);
|
||||
// Add the point to a list of free points.
|
||||
void DisposeOutPt(OutPt *pt) { pt->Next = m_OutPtsFree; m_OutPtsFree = pt; }
|
||||
void DisposeOutPts(OutPt*& pp) { if (pp != nullptr) { pp->Prev->Next = m_OutPtsFree; m_OutPtsFree = pp; } }
|
||||
void DisposeAllOutRecs();
|
||||
bool ProcessIntersections(const cInt topY);
|
||||
void BuildIntersectList(const cInt topY);
|
||||
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
|
||||
void BuildResult(Paths& polys);
|
||||
void BuildResult2(PolyTree& polytree);
|
||||
void SetHoleState(TEdge *e, OutRec *outrec);
|
||||
bool FixupIntersectionOrder();
|
||||
void FixupOutPolygon(OutRec &outrec);
|
||||
void FixupOutPolyline(OutRec &outrec);
|
||||
bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
|
||||
void FixHoleLinkage(OutRec &outrec);
|
||||
bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2);
|
||||
bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, const IntPoint &Pt, bool DiscardLeft);
|
||||
void JoinCommonEdges();
|
||||
void DoSimplePolygons();
|
||||
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec);
|
||||
void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
#ifdef CLIPPERLIB_USE_XYZ
|
||||
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
|
||||
#endif
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ClipperOffset
|
||||
{
|
||||
public:
|
||||
ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25, double shortestEdgeLength = 0.) :
|
||||
MiterLimit(miterLimit), ArcTolerance(roundPrecision), ShortestEdgeLength(shortestEdgeLength), m_lowest(-1, 0) {}
|
||||
~ClipperOffset() { Clear(); }
|
||||
void AddPath(const Path& path, JoinType joinType, EndType endType);
|
||||
template<typename PathsProvider>
|
||||
void AddPaths(PathsProvider &&paths, JoinType joinType, EndType endType) {
|
||||
for (const Path &path : paths)
|
||||
AddPath(path, joinType, endType);
|
||||
}
|
||||
void Execute(Paths& solution, double delta);
|
||||
void Execute(PolyTree& solution, double delta);
|
||||
void Clear();
|
||||
double MiterLimit;
|
||||
double ArcTolerance;
|
||||
double ShortestEdgeLength;
|
||||
|
||||
private:
|
||||
Paths m_destPolys;
|
||||
Path m_srcPoly;
|
||||
Path m_destPoly;
|
||||
std::vector<DoublePoint, Allocator<DoublePoint>> m_normals;
|
||||
double m_delta, m_sinA, m_sin, m_cos;
|
||||
double m_miterLim, m_StepsPerRad;
|
||||
// x: index of the lowest contour in m_polyNodes
|
||||
// y: index of the lowest point in the lowest contour
|
||||
IntPoint m_lowest;
|
||||
PolyNode m_polyNodes;
|
||||
|
||||
void FixOrientations();
|
||||
void DoOffset(double delta);
|
||||
void OffsetPoint(int j, int& k, JoinType jointype);
|
||||
void DoSquare(int j, int k);
|
||||
void DoMiter(int j, int k, double r);
|
||||
void DoRound(int j, int k);
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class clipperException : public std::exception
|
||||
{
|
||||
public:
|
||||
clipperException(const char* description): m_descr(description) {}
|
||||
virtual ~clipperException() throw() {}
|
||||
virtual const char* what() const throw() {return m_descr.c_str();}
|
||||
private:
|
||||
std::string m_descr;
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Union with "strictly simple" fix enabled.
|
||||
template<typename PathsProvider>
|
||||
inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftNonZero, bool strictly_simple = true) {
|
||||
Clipper c;
|
||||
c.StrictlySimple(strictly_simple);
|
||||
c.AddPaths(std::forward<PathsProvider>(in_polys), ptSubject, true);
|
||||
Paths out;
|
||||
c.Execute(ctUnion, out, fillType, fillType);
|
||||
return out;
|
||||
}
|
||||
|
||||
} //ClipperLib namespace
|
||||
|
||||
#ifdef CLIPPERLIB_NAMESPACE_PREFIX
|
||||
} // namespace CLIPPERLIB_NAMESPACE_PREFIX
|
||||
#endif // CLIPPERLIB_NAMESPACE_PREFIX
|
||||
|
||||
#endif //clipper_hpp
|
||||
@@ -1,7 +0,0 @@
|
||||
// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
|
||||
|
||||
// Enable the Z coordinate support.
|
||||
#define CLIPPERLIB_USE_XYZ
|
||||
|
||||
// and let it compile
|
||||
#include "clipper.cpp"
|
||||
@@ -1,18 +0,0 @@
|
||||
// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
|
||||
|
||||
#ifndef clipper_z_hpp
|
||||
#ifdef clipper_hpp
|
||||
#error "You should include clipper_z.hpp before clipper.hpp"
|
||||
#endif
|
||||
|
||||
#define clipper_z_hpp
|
||||
|
||||
// Enable the Z coordinate support.
|
||||
#define CLIPPERLIB_USE_XYZ
|
||||
|
||||
#include "clipper.hpp"
|
||||
|
||||
#undef clipper_hpp
|
||||
#undef CLIPPERLIB_USE_XYZ
|
||||
|
||||
#endif // clipper_z_hpp
|
||||
@@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(Clipper2 VERSION 1.5.2 LANGUAGES C CXX)
|
||||
project(Clipper2 VERSION 2.0.1 LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
@@ -19,6 +19,7 @@ set(CLIPPER2_INC
|
||||
Clipper2Lib/include/clipper2/clipper.minkowski.h
|
||||
Clipper2Lib/include/clipper2/clipper.offset.h
|
||||
Clipper2Lib/include/clipper2/clipper.rectclip.h
|
||||
Clipper2Lib/include/clipper2/clipper.triangulation.h
|
||||
Clipper2Lib/include/clipper2/clipper2_z.hpp
|
||||
)
|
||||
|
||||
@@ -26,6 +27,7 @@ set(CLIPPER2_SRC
|
||||
Clipper2Lib/src/clipper.engine.cpp
|
||||
Clipper2Lib/src/clipper.offset.cpp
|
||||
Clipper2Lib/src/clipper.rectclip.cpp
|
||||
Clipper2Lib/src/clipper.triangulation.cpp
|
||||
Clipper2Lib/src/clipper2_z.cpp
|
||||
)
|
||||
|
||||
@@ -36,6 +38,9 @@ target_include_directories(Clipper2
|
||||
PUBLIC Clipper2Lib/include
|
||||
)
|
||||
|
||||
# Engine nodes are allocated through tbbmalloc (see clipper.engine.cpp).
|
||||
target_link_libraries(Clipper2 PRIVATE TBB::tbbmalloc)
|
||||
|
||||
if (WIN32)
|
||||
if (MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
target_compile_options(Clipper2 PRIVATE /W4 /WX)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 12 May 2024 *
|
||||
* Date : 12 October 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Core Clipper Library structures and functions *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -251,6 +251,20 @@ namespace Clipper2Lib {
|
||||
template <typename T>
|
||||
using Paths = std::vector<Path<T>>;
|
||||
|
||||
template <typename T, typename T2=T>
|
||||
Path<T>& operator<<(Path<T>& poly, const Point<T2>& p)
|
||||
{
|
||||
poly.emplace_back(p);
|
||||
return poly;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Paths<T>& operator<<(Paths<T>& polys, const Path<T>& p)
|
||||
{
|
||||
polys.emplace_back(p);
|
||||
return polys;
|
||||
}
|
||||
|
||||
using Path64 = Path<int64_t>;
|
||||
using PathD = Path<double>;
|
||||
using Paths64 = std::vector< Path64>;
|
||||
@@ -685,32 +699,31 @@ namespace Clipper2Lib {
|
||||
|
||||
inline int TriSign(int64_t x) // returns 0, 1 or -1
|
||||
{
|
||||
return (x > 0) - (x < 0);
|
||||
return (x > 0) - (x < 0);
|
||||
}
|
||||
|
||||
struct MultiplyUInt64Result
|
||||
struct UInt128Struct
|
||||
{
|
||||
const uint64_t result = 0;
|
||||
const uint64_t carry = 0;
|
||||
const uint64_t lo = 0;
|
||||
const uint64_t hi = 0;
|
||||
|
||||
bool operator==(const MultiplyUInt64Result& other) const
|
||||
bool operator==(const UInt128Struct& other) const
|
||||
{
|
||||
return result == other.result && carry == other.carry;
|
||||
return lo == other.lo && hi == other.hi;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
inline MultiplyUInt64Result Multiply(uint64_t a, uint64_t b) // #834, #835
|
||||
inline UInt128Struct MultiplyUInt64(uint64_t a, uint64_t b) // #834, #835
|
||||
{
|
||||
// note to self - lamba expressions follow
|
||||
const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
|
||||
const auto hi = [](uint64_t x) { return x >> 32; };
|
||||
|
||||
const uint64_t x1 = lo(a) * lo(b);
|
||||
const uint64_t x2 = hi(a) * lo(b) + hi(x1);
|
||||
const uint64_t x3 = lo(a) * hi(b) + lo(x2);
|
||||
const uint64_t result = lo(x3) << 32 | lo(x1);
|
||||
const uint64_t carry = hi(a) * hi(b) + hi(x2) + hi(x3);
|
||||
|
||||
return { result, carry };
|
||||
return { uint64_t(lo(x3) << 32 | lo(x1)), uint64_t(hi(a) * hi(b) + hi(x2) + hi(x3)) };
|
||||
}
|
||||
|
||||
// returns true if (and only if) a * b == c * d
|
||||
@@ -727,14 +740,50 @@ namespace Clipper2Lib {
|
||||
const auto abs_c = static_cast<uint64_t>(std::abs(c));
|
||||
const auto abs_d = static_cast<uint64_t>(std::abs(d));
|
||||
|
||||
const auto abs_ab = Multiply(abs_a, abs_b);
|
||||
const auto abs_cd = Multiply(abs_c, abs_d);
|
||||
const auto ab = MultiplyUInt64(abs_a, abs_b);
|
||||
const auto cd = MultiplyUInt64(abs_c, abs_d);
|
||||
|
||||
// nb: it's important to differentiate 0 values here from other values
|
||||
const auto sign_ab = TriSign(a) * TriSign(b);
|
||||
const auto sign_cd = TriSign(c) * TriSign(d);
|
||||
|
||||
return abs_ab == abs_cd && sign_ab == sign_cd;
|
||||
return ab == cd && sign_ab == sign_cd;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline int CrossProductSign(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3)
|
||||
{
|
||||
const auto a = pt2.x - pt1.x;
|
||||
const auto b = pt3.y - pt2.y;
|
||||
const auto c = pt2.y - pt1.y;
|
||||
const auto d = pt3.x - pt2.x;
|
||||
|
||||
#if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX
|
||||
const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b);
|
||||
const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d);
|
||||
if (ab > cd) return 1;
|
||||
else if (ab < cd) return -1;
|
||||
else return 0;
|
||||
#else
|
||||
const auto ab = MultiplyUInt64(std::abs(a), std::abs(b));
|
||||
const auto cd = MultiplyUInt64(std::abs(c), std::abs(d));
|
||||
|
||||
const auto sign_ab = TriSign(a) * TriSign(b);
|
||||
const auto sign_cd = TriSign(c) * TriSign(d);
|
||||
|
||||
if (sign_ab == sign_cd)
|
||||
{
|
||||
int result;
|
||||
if (ab.hi == cd.hi)
|
||||
{
|
||||
if (ab.lo == cd.lo) return 0;
|
||||
result = (ab.lo > cd.lo) ? 1 : -1;
|
||||
}
|
||||
else result = (ab.hi > cd.hi) ? 1 : -1;
|
||||
return (sign_ab > 0) ? result : -result;
|
||||
}
|
||||
return (sign_ab > sign_cd) ? 1 : -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -838,6 +887,10 @@ namespace Clipper2Lib {
|
||||
return Area<T>(poly) >= 0;
|
||||
}
|
||||
|
||||
// GetLineIntersectPt - a 'true' result is non-parallel. The 'ip' will also
|
||||
// be constrained to seg1. However, it's possible that 'ip' won't be inside
|
||||
// seg2, even when 'ip' hasn't been constrained (ie 'ip' is inside seg1).
|
||||
|
||||
#if CLIPPER2_HI_PRECISION
|
||||
// caution: this will compromise performance
|
||||
// https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253
|
||||
@@ -845,7 +898,7 @@ namespace Clipper2Lib {
|
||||
#define CC_MIN(x,y) ((x)>(y)?(y):(x))
|
||||
#define CC_MAX(x,y) ((x)<(y)?(y):(x))
|
||||
template<typename T>
|
||||
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
|
||||
{
|
||||
double ln1dy = static_cast<double>(ln1b.y - ln1a.y);
|
||||
@@ -891,11 +944,14 @@ namespace Clipper2Lib {
|
||||
ip.x = originx + static_cast<T>(hitx);
|
||||
ip.y = originy + static_cast<T>(hity);
|
||||
}
|
||||
#ifdef USINGZ
|
||||
ip.z = 0;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
|
||||
@@ -913,7 +969,10 @@ namespace Clipper2Lib {
|
||||
{
|
||||
ip.x = static_cast<T>(ln1a.x + t * dx1);
|
||||
ip.y = static_cast<T>(ln1a.y + t * dy1);
|
||||
}
|
||||
#ifdef USINGZ
|
||||
ip.z = 0;
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -940,30 +999,53 @@ namespace Clipper2Lib {
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline int GetSign(const T& val)
|
||||
{
|
||||
if (!val) return 0;
|
||||
inline int GetSign(const T& val)
|
||||
{
|
||||
if (!val) return 0;
|
||||
return (val > 0) ? 1 : -1;
|
||||
}
|
||||
|
||||
inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
|
||||
const Point64& seg2a, const Point64& seg2b, bool inclusive = false)
|
||||
{
|
||||
double dy1 = static_cast<double>(seg1b.y - seg1a.y);
|
||||
double dx1 = static_cast<double>(seg1b.x - seg1a.x);
|
||||
double dy2 = static_cast<double>(seg2b.y - seg2a.y);
|
||||
double dx2 = static_cast<double>(seg2b.x - seg2a.x);
|
||||
double cp = dy1 * dx2 - dy2 * dx1;
|
||||
if (cp == 0) return false; // ie parallel segments
|
||||
|
||||
if (inclusive)
|
||||
{
|
||||
double res1 = CrossProduct(seg1a, seg2a, seg2b);
|
||||
double res2 = CrossProduct(seg1b, seg2a, seg2b);
|
||||
if (res1 * res2 > 0) return false;
|
||||
double res3 = CrossProduct(seg2a, seg1a, seg1b);
|
||||
double res4 = CrossProduct(seg2b, seg1a, seg1b);
|
||||
if (res3 * res4 > 0) return false;
|
||||
return (res1 || res2 || res3 || res4); // ensures not collinear
|
||||
//result **includes** segments that touch at an end point
|
||||
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
|
||||
if (t == 0) return true;
|
||||
if (t > 0)
|
||||
{
|
||||
if (cp < 0 || t > cp) return false;
|
||||
}
|
||||
else if (cp > 0 || t < cp) return false; // false when t more neg. than cp
|
||||
|
||||
t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
|
||||
if (t == 0) return true;
|
||||
if (t > 0) return (cp > 0 && t <= cp);
|
||||
else return (cp < 0 && t >= cp); // true when t less neg. than cp
|
||||
}
|
||||
else {
|
||||
return (GetSign(CrossProduct(seg1a, seg2a, seg2b)) *
|
||||
GetSign(CrossProduct(seg1b, seg2a, seg2b)) < 0) &&
|
||||
(GetSign(CrossProduct(seg2a, seg1a, seg1b)) *
|
||||
GetSign(CrossProduct(seg2b, seg1a, seg1b)) < 0);
|
||||
else
|
||||
{
|
||||
//result **excludes** segments that touch at an end point
|
||||
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
|
||||
if (t == 0) return false;
|
||||
if (t > 0)
|
||||
{
|
||||
if (cp < 0 || t >= cp) return false;
|
||||
}
|
||||
else if (cp > 0 || t <= cp ) return false; // false when t more neg. than cp
|
||||
|
||||
t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
|
||||
if (t == 0) return false;
|
||||
if (t > 0) return (cp > 0 && t < cp);
|
||||
else return (cp < 0 && t > cp); // true when t less neg. than cp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1051,7 +1133,7 @@ namespace Clipper2Lib {
|
||||
val = 1 - val; // toggle val
|
||||
else
|
||||
{
|
||||
double d = CrossProduct(*prev, *curr, pt);
|
||||
int d = CrossProductSign(*prev, *curr, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
@@ -1065,7 +1147,7 @@ namespace Clipper2Lib {
|
||||
if (curr == cend) curr = cbegin;
|
||||
if (curr == cbegin) prev = cend - 1;
|
||||
else prev = curr - 1;
|
||||
double d = CrossProduct(*prev, *curr, pt);
|
||||
int d = CrossProductSign(*prev, *curr, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
// Orca: engine nodes are allocated through tbbmalloc, see clipper.engine.cpp.
|
||||
#define CLIPPER2_NODE_ALLOCATOR \
|
||||
static void* operator new(size_t size); \
|
||||
static void operator delete(void* ptr) noexcept; \
|
||||
static void* operator new[](size_t size); \
|
||||
static void operator delete[](void* ptr) noexcept;
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
@@ -50,6 +57,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
|
||||
struct Vertex {
|
||||
CLIPPER2_NODE_ALLOCATOR
|
||||
Point64 pt;
|
||||
Vertex* next = nullptr;
|
||||
Vertex* prev = nullptr;
|
||||
@@ -57,6 +65,7 @@ namespace Clipper2Lib {
|
||||
};
|
||||
|
||||
struct OutPt {
|
||||
CLIPPER2_NODE_ALLOCATOR
|
||||
Point64 pt;
|
||||
OutPt* next = nullptr;
|
||||
OutPt* prev = nullptr;
|
||||
@@ -81,6 +90,7 @@ namespace Clipper2Lib {
|
||||
//OutRec: contains a path in the clipping solution. Edges in the AEL will
|
||||
//have OutRec pointers assigned when they form part of the clipping solution.
|
||||
struct OutRec {
|
||||
CLIPPER2_NODE_ALLOCATOR
|
||||
size_t idx = 0;
|
||||
OutRec* owner = nullptr;
|
||||
Active* front_edge = nullptr;
|
||||
@@ -106,6 +116,7 @@ namespace Clipper2Lib {
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
struct Active {
|
||||
CLIPPER2_NODE_ALLOCATOR
|
||||
Point64 bot;
|
||||
Point64 top;
|
||||
int64_t curr_x = 0; //current (updated at every new scanline)
|
||||
@@ -133,6 +144,7 @@ namespace Clipper2Lib {
|
||||
};
|
||||
|
||||
struct LocalMinima {
|
||||
CLIPPER2_NODE_ALLOCATOR
|
||||
Vertex* vertex;
|
||||
PathType polytype;
|
||||
bool is_open;
|
||||
@@ -303,6 +315,7 @@ namespace Clipper2Lib {
|
||||
protected:
|
||||
PolyPath* parent_;
|
||||
public:
|
||||
CLIPPER2_NODE_ALLOCATOR
|
||||
PolyPath(PolyPath* parent = nullptr): parent_(parent){}
|
||||
virtual ~PolyPath() {};
|
||||
//https://en.cppreference.com/w/cpp/language/rule_of_three
|
||||
@@ -330,15 +343,16 @@ namespace Clipper2Lib {
|
||||
//Even levels except level 0
|
||||
return lvl && !(lvl & 1);
|
||||
}
|
||||
template<typename T>
|
||||
static double Clipper2LibArea(const Path<T> &poly)
|
||||
{
|
||||
// Area() of the namespace this header is compiled into (Clipper2Lib or Clipper2Lib_Z).
|
||||
template<typename T>
|
||||
static double Clipper2LibArea(const Path<T> &poly)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
return Clipper2Lib_Z::Area<T>(poly);
|
||||
return Clipper2Lib_Z::Area<T>(poly);
|
||||
#else
|
||||
return Clipper2Lib::Area<T>(poly);
|
||||
return Clipper2Lib::Area<T>(poly);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
|
||||
@@ -388,7 +402,8 @@ namespace Clipper2Lib {
|
||||
|
||||
double Area() const
|
||||
{
|
||||
return std::accumulate(childs_.cbegin(), childs_.cend(), Clipper2LibArea<int64_t>(polygon_),
|
||||
return std::accumulate(childs_.cbegin(), childs_.cend(),
|
||||
Clipper2LibArea<int64_t>(polygon_),
|
||||
[](double a, const auto& child) {return a + child->Area(); });
|
||||
}
|
||||
|
||||
@@ -462,7 +477,8 @@ namespace Clipper2Lib {
|
||||
|
||||
double Area() const
|
||||
{
|
||||
return std::accumulate(childs_.begin(), childs_.end(), Clipper2LibArea<double>(polygon_),
|
||||
return std::accumulate(childs_.begin(), childs_.end(),
|
||||
Clipper2LibArea<double>(polygon_),
|
||||
[](double a, const auto& child) {return a + child->Area(); });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,17 +19,17 @@
|
||||
|
||||
The path structures used extensively in other parts of this library are all
|
||||
based on std::vector classes. Since C++ classes can't be accessed by other
|
||||
languages, these paths are exported here as very simple array structures
|
||||
(either of int64_t or double) that can be parsed by just about any
|
||||
languages, these paths are exported here as very simple array structures
|
||||
(either of int64_t or double) that can be parsed by just about any
|
||||
programming language.
|
||||
|
||||
These 2D paths are defined by series of x and y coordinates together with an
|
||||
optional user-defined 'z' value (see Z-values below). Hence, a vertex refers
|
||||
to a single x and y coordinate (+/- a user-defined value). Data structures
|
||||
have names with suffixes that indicate the array type (either int64_t or
|
||||
double). For example, the data structure CPath64 contains an array of int64_t
|
||||
values, whereas the data structure CPathD contains an array of double.
|
||||
Where documentation omits the type suffix (eg CPath), it is referring to an
|
||||
to a single x and y coordinate (+/- a user-defined value). Data structures
|
||||
have names with suffixes that indicate the array type (either int64_t or
|
||||
double). For example, the data structure CPath64 contains an array of int64_t
|
||||
values, whereas the data structure CPathD contains an array of double.
|
||||
Where documentation omits the type suffix (eg CPath), it is referring to an
|
||||
array whose data type could be either int64_t or double.
|
||||
|
||||
For conciseness, the following letters are used in the diagrams below:
|
||||
@@ -39,10 +39,10 @@ A: Number of elements in an array
|
||||
|
||||
|
||||
CPath64 and CPathD:
|
||||
These are arrays of either int64_t or double values. Apart from
|
||||
the first two elements, these arrays are a series of vertices
|
||||
that together define a path. The very first element contains the
|
||||
number of vertices (N) in the path, while second element should
|
||||
These are arrays of either int64_t or double values. Apart from
|
||||
the first two elements, these arrays are a series of vertices
|
||||
that together define a path. The very first element contains the
|
||||
number of vertices (N) in the path, while second element should
|
||||
contain a 0 value.
|
||||
_______________________________________________________________
|
||||
| counters | vertex1 | vertex2 | ... | vertexN |
|
||||
@@ -52,9 +52,9 @@ _______________________________________________________________
|
||||
|
||||
CPaths64 and CPathsD:
|
||||
These are also arrays of either int64_t or double values that
|
||||
contain any number of consecutive CPath structures. However,
|
||||
contain any number of consecutive CPath structures. However,
|
||||
preceding the first path is a pair of values. The first value
|
||||
contains the length of the entire array structure (A), and the
|
||||
contains the length of the entire array structure (A), and the
|
||||
second contains the number (ie count) of contained paths (C).
|
||||
Memory allocation for CPaths64 = A * sizeof(int64_t)
|
||||
Memory allocation for CPathsD = A * sizeof(double)
|
||||
@@ -65,12 +65,12 @@ __________________________________________
|
||||
|
||||
|
||||
CPolytree64 and CPolytreeD:
|
||||
The entire polytree structure is an array of int64_t or double. The
|
||||
first element in the array indicates the array's total length (A).
|
||||
The second element indicates the number (C) of CPolyPath structures
|
||||
The entire polytree structure is an array of int64_t or double. The
|
||||
first element in the array indicates the array's total length (A).
|
||||
The second element indicates the number (C) of CPolyPath structures
|
||||
that are the TOP LEVEL CPolyPath in the polytree, and these top
|
||||
level CPolyPath immediately follow these first two array elements.
|
||||
These top level CPolyPath structures may, in turn, contain nested
|
||||
level CPolyPath immediately follow these first two array elements.
|
||||
These top level CPolyPath structures may, in turn, contain nested
|
||||
CPolyPath children, and these collectively make a tree structure.
|
||||
_________________________________________________________
|
||||
| counters | CPolyPath1 | CPolyPath2 | ... | CPolyPathC |
|
||||
@@ -116,13 +116,10 @@ the four vertices that define the two segments that are intersecting.
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "clipper2/clipper.rectclip.h"
|
||||
#include "clipper2/clipper.triangulation.h"
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
typedef int64_t* CPath64;
|
||||
typedef int64_t* CPaths64;
|
||||
@@ -254,9 +251,9 @@ ZCallback64 dllCallback64 = nullptr;
|
||||
ZCallbackD dllCallbackD = nullptr;
|
||||
|
||||
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 3;
|
||||
#else
|
||||
#else
|
||||
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 2;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
static void GetPathCountAndCPathsArrayLen(const Paths<T>& paths,
|
||||
@@ -396,7 +393,7 @@ static Path<T> ConvertCPathToPathT(T* path)
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
result.emplace_back(x, y, z);
|
||||
#else
|
||||
#else
|
||||
result.emplace_back(x, y);
|
||||
#endif
|
||||
}
|
||||
@@ -414,7 +411,7 @@ static Paths<T> ConvertCPathsToPathsT(T* paths)
|
||||
for (size_t i = 0; i < cnt; ++i)
|
||||
{
|
||||
size_t cnt2 = static_cast<size_t>(*v);
|
||||
v += 2;
|
||||
v += 2;
|
||||
Path<T> path;
|
||||
path.reserve(cnt2);
|
||||
for (size_t j = 0; j < cnt2; ++j)
|
||||
@@ -447,7 +444,7 @@ static Path64 ConvertCPathDToPath64WithScale(const CPathD path, double scale)
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
result.emplace_back(x, y, z);
|
||||
#else
|
||||
#else
|
||||
result.emplace_back(x, y);
|
||||
#endif
|
||||
}
|
||||
@@ -492,7 +489,7 @@ static void CreateCPolyPath64(const PolyPath64* pp, int64_t*& v)
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
#ifdef USINGZ
|
||||
* v++ = Reinterpret<int64_t>(pt.z); // raw memory copy
|
||||
#endif
|
||||
}
|
||||
@@ -508,7 +505,7 @@ static void CreateCPolyPathD(const PolyPathD* pp, double*& v)
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
#ifdef USINGZ
|
||||
* v++ = Reinterpret<double>(pt.z); // raw memory copy
|
||||
#endif
|
||||
}
|
||||
@@ -816,6 +813,24 @@ EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath6
|
||||
return CreateCPathsFromPathsT(solution);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 Triangulate64(const CPaths64 paths, bool use_delaunay)
|
||||
{
|
||||
Paths64 pp = ConvertCPathsToPathsT(paths);
|
||||
Paths64 sol;
|
||||
if (Triangulate(pp, sol, use_delaunay) != TriangulateResult::success) return nullptr;
|
||||
return CreateCPathsFromPathsT(sol);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD TriangulateD(const CPathsD paths, int decimal_precison, bool use_delaunay)
|
||||
{
|
||||
if (decimal_precison < -8 || decimal_precison > 8) return nullptr;
|
||||
const double scale = std::pow(10, decimal_precison);
|
||||
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
|
||||
Paths64 sol;
|
||||
if (Triangulate(pp, sol, use_delaunay) != TriangulateResult::success) return nullptr;
|
||||
return CreateCPathsDFromPaths64(sol, 1 / scale);
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt);
|
||||
typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 27 April 2024 *
|
||||
* Date : 5 March 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : This module provides a simple interface to the Clipper Library *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -13,14 +13,15 @@
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "clipper2/clipper.minkowski.h"
|
||||
#include "clipper2/clipper.rectclip.h"
|
||||
#include "clipper2/clipper.minkowski.h"
|
||||
#include "clipper2/clipper.triangulation.h"
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
|
||||
@@ -154,14 +155,14 @@
|
||||
if (!delta) return paths;
|
||||
if (error_code) return PathsD();
|
||||
const double scale = std::pow(10, precision);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance * scale);
|
||||
clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
|
||||
if (error_code) return PathsD();
|
||||
Paths64 solution;
|
||||
clip_offset.Execute(delta * scale, solution);
|
||||
return ScalePaths<double, int64_t>(solution, 1 / scale, error_code);
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> TranslatePath(const Path<T>& path, T dx, T dy)
|
||||
{
|
||||
@@ -355,6 +356,29 @@
|
||||
#endif
|
||||
}
|
||||
|
||||
inline size_t GetNext(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
++current;
|
||||
while (current <= high && flags[current]) ++current;
|
||||
if (current <= high) return current;
|
||||
current = 0;
|
||||
while (flags[current]) ++current;
|
||||
return current;
|
||||
}
|
||||
|
||||
inline size_t GetPrior(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
if (current == 0) current = high;
|
||||
else --current;
|
||||
while (current > 0 && flags[current]) --current;
|
||||
if (!flags[current]) return current;
|
||||
current = high;
|
||||
while (flags[current]) --current;
|
||||
return current;
|
||||
}
|
||||
|
||||
} // end details namespace
|
||||
|
||||
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
|
||||
@@ -615,29 +639,6 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
inline size_t GetNext(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
++current;
|
||||
while (current <= high && flags[current]) ++current;
|
||||
if (current <= high) return current;
|
||||
current = 0;
|
||||
while (flags[current]) ++current;
|
||||
return current;
|
||||
}
|
||||
|
||||
inline size_t GetPrior(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
if (current == 0) current = high;
|
||||
else --current;
|
||||
while (current > 0 && flags[current]) --current;
|
||||
if (!flags[current]) return current;
|
||||
current = high;
|
||||
while (flags[current]) --current;
|
||||
return current;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> SimplifyPath(const Path<T> &path,
|
||||
double epsilon, bool isClosedPath = true)
|
||||
@@ -669,13 +670,13 @@
|
||||
start = curr;
|
||||
do
|
||||
{
|
||||
curr = GetNext(curr, high, flags);
|
||||
curr = details::GetNext(curr, high, flags);
|
||||
} while (curr != start && distSqr[curr] > epsSqr);
|
||||
if (curr == start) break;
|
||||
}
|
||||
|
||||
prior = GetPrior(curr, high, flags);
|
||||
next = GetNext(curr, high, flags);
|
||||
prior = details::GetPrior(curr, high, flags);
|
||||
next = details::GetNext(curr, high, flags);
|
||||
if (next == prior) break;
|
||||
|
||||
// flag for removal the smaller of adjacent 'distances'
|
||||
@@ -684,14 +685,14 @@
|
||||
prior2 = prior;
|
||||
prior = curr;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
next = details::GetNext(next, high, flags);
|
||||
}
|
||||
else
|
||||
prior2 = GetPrior(prior, high, flags);
|
||||
prior2 = details::GetPrior(prior, high, flags);
|
||||
|
||||
flags[curr] = true;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
next = details::GetNext(next, high, flags);
|
||||
|
||||
if (isClosedPath || ((curr != high) && (curr != 0)))
|
||||
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
|
||||
@@ -716,6 +717,35 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline bool Path2ContainsPath1(const Path<T>& path1, const Path<T>& path2)
|
||||
{
|
||||
// precondition: paths must not intersect, except for
|
||||
// transient (and presumed 'micro') path intersections
|
||||
PointInPolygonResult pip = PointInPolygonResult::IsOn;
|
||||
for (const Point<T>& pt : path1)
|
||||
{
|
||||
switch (PointInPolygon(pt, path2))
|
||||
{
|
||||
case PointInPolygonResult::IsOutside:
|
||||
if (pip == PointInPolygonResult::IsOutside) return false;
|
||||
pip = PointInPolygonResult::IsOutside;
|
||||
break;
|
||||
case PointInPolygonResult::IsInside:
|
||||
if (pip == PointInPolygonResult::IsInside) return true;
|
||||
pip = PointInPolygonResult::IsInside;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pip != PointInPolygonResult::IsInside) return false;
|
||||
// result is likely true but check midpoint
|
||||
Point<T> mp1 = GetBounds(path1).MidPoint();
|
||||
return PointInPolygon(mp1, path2) == PointInPolygonResult::IsInside;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void RDP(const Path<T> path, std::size_t begin,
|
||||
std::size_t end, double epsSqrd, std::vector<bool>& flags)
|
||||
|
||||
@@ -39,7 +39,7 @@ private:
|
||||
class Group {
|
||||
public:
|
||||
Paths64 paths_in;
|
||||
std::optional<size_t> lowest_path_idx{};
|
||||
std::optional<size_t> lowest_path_idx{};
|
||||
bool is_reversed = false;
|
||||
JoinType join_type;
|
||||
EndType end_type;
|
||||
@@ -100,7 +100,7 @@ public:
|
||||
void AddPath(const Path64& path, JoinType jt_, EndType et_);
|
||||
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
|
||||
void Clear() { groups_.clear(); norms.clear(); };
|
||||
|
||||
|
||||
void Execute(double delta, Paths64& sols_64);
|
||||
void Execute(double delta, PolyTree64& polytree);
|
||||
void Execute(DeltaCallback64 delta_cb, Paths64& paths);
|
||||
@@ -114,7 +114,7 @@ public:
|
||||
|
||||
bool PreserveCollinear() const { return preserve_collinear_; }
|
||||
void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
|
||||
|
||||
|
||||
bool ReverseSolution() const { return reverse_solution_; }
|
||||
void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 6 December 2025 *
|
||||
* Release : BETA RELEASE *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Delaunay Triangulation *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_TRIANGULATION_H
|
||||
#define CLIPPER_TRIANGULATION_H
|
||||
|
||||
#include <stack>
|
||||
#include "clipper2/clipper.core.h"
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
enum class TriangulateResult { success, fail, no_polygons, paths_intersect };
|
||||
|
||||
// Triangulate - this function will not accept intesecting paths
|
||||
TriangulateResult Triangulate(const Paths64& pp, Paths64& solution, bool useDelaunay = true);
|
||||
TriangulateResult Triangulate(const PathsD& pp, int decPlaces, PathsD& solution, bool useDelaunay = true);
|
||||
|
||||
} // Clipper2Lib namespace
|
||||
#endif // CLIPPER_TRIANGULATION_H
|
||||
@@ -1,6 +1,6 @@
|
||||
#ifndef CLIPPER_VERSION_H
|
||||
#define CLIPPER_VERSION_H
|
||||
|
||||
constexpr auto CLIPPER2_VERSION = "1.5.2";
|
||||
constexpr auto CLIPPER2_VERSION = "2.0.1";
|
||||
|
||||
#endif // CLIPPER_VERSION_H
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 17 September 2024 *
|
||||
* Date : 5 November 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : This is the main polygon clipping module *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.h"
|
||||
#include <stdexcept>
|
||||
#include <new>
|
||||
#include <oneapi/tbb/scalable_allocator.h>
|
||||
|
||||
// https://github.com/AngusJohnson/Clipper2/discussions/334
|
||||
// #discussioncomment-4248602
|
||||
@@ -27,10 +29,30 @@ namespace Clipper2Lib_Z {
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
// Orca: tbbmalloc scales far better than the default heap when all slicing threads clip at once.
|
||||
static void* NodeAlloc(size_t size)
|
||||
{
|
||||
if (void* p = scalable_malloc(size)) return p;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
#define CLIPPER2_DEFINE_NODE_ALLOCATOR(T) \
|
||||
void* T::operator new(size_t size) { return NodeAlloc(size); } \
|
||||
void T::operator delete(void* ptr) noexcept { scalable_free(ptr); } \
|
||||
void* T::operator new[](size_t size) { return NodeAlloc(size); } \
|
||||
void T::operator delete[](void* ptr) noexcept { scalable_free(ptr); }
|
||||
CLIPPER2_DEFINE_NODE_ALLOCATOR(Vertex)
|
||||
CLIPPER2_DEFINE_NODE_ALLOCATOR(OutPt)
|
||||
CLIPPER2_DEFINE_NODE_ALLOCATOR(OutRec)
|
||||
CLIPPER2_DEFINE_NODE_ALLOCATOR(Active)
|
||||
CLIPPER2_DEFINE_NODE_ALLOCATOR(LocalMinima)
|
||||
CLIPPER2_DEFINE_NODE_ALLOCATOR(PolyPath)
|
||||
#undef CLIPPER2_DEFINE_NODE_ALLOCATOR
|
||||
|
||||
static const Rect64 invalid_rect = Rect64(false);
|
||||
|
||||
// Every closed path (ie polygon) is made up of a series of vertices forming edge
|
||||
// 'bounds' that alternate between ascending bounds (containing edges going up
|
||||
// Every closed path (ie polygon) is made up of a series of vertices forming edge
|
||||
// 'bounds' that alternate between ascending bounds (containing edges going up
|
||||
// relative to the Y-axis) and descending bounds. 'Local Minima' refers to
|
||||
// vertices where ascending and descending bounds join at the bottom, and
|
||||
// 'Local Maxima' are where ascending and descending bounds join at the top.
|
||||
@@ -482,8 +504,7 @@ namespace Clipper2Lib {
|
||||
inline void SetOwner(OutRec* outrec, OutRec* new_owner)
|
||||
{
|
||||
//precondition1: new_owner is never null
|
||||
while (new_owner->owner && !new_owner->owner->pts)
|
||||
new_owner->owner = new_owner->owner->owner;
|
||||
new_owner->owner = GetRealOutRec(new_owner->owner);
|
||||
OutRec* tmp = new_owner;
|
||||
while (tmp && tmp != outrec) tmp = tmp->owner;
|
||||
if (tmp) new_owner->owner = outrec->owner;
|
||||
@@ -536,9 +557,9 @@ namespace Clipper2Lib {
|
||||
val = 1 - val; // toggle val
|
||||
else
|
||||
{
|
||||
double d = CrossProduct(op2->prev->pt, op2->pt, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
|
||||
if (i == 0) return PointInPolygonResult::IsOn;
|
||||
if ((i < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
is_above = !is_above;
|
||||
op2 = op2->next;
|
||||
@@ -546,9 +567,9 @@ namespace Clipper2Lib {
|
||||
|
||||
if (is_above != starting_above)
|
||||
{
|
||||
double d = CrossProduct(op2->prev->pt, op2->pt, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
|
||||
if (i == 0) return PointInPolygonResult::IsOn;
|
||||
if ((i < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
|
||||
if (val == 0) return PointInPolygonResult::IsOutside;
|
||||
@@ -578,30 +599,31 @@ namespace Clipper2Lib {
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool Path1InsidePath2(OutPt* op1, OutPt* op2)
|
||||
inline bool Path2ContainsPath1(OutPt* op1, OutPt* op2)
|
||||
{
|
||||
// we need to make some accommodation for rounding errors
|
||||
// so we won't jump if the first vertex is found outside
|
||||
PointInPolygonResult result;
|
||||
int outside_cnt = 0;
|
||||
// this function accommodates rounding errors that
|
||||
// can cause path micro intersections
|
||||
PointInPolygonResult pip = PointInPolygonResult::IsOn;
|
||||
OutPt* op = op1;
|
||||
do
|
||||
{
|
||||
result = PointInOpPolygon(op->pt, op2);
|
||||
if (result == PointInPolygonResult::IsOutside) ++outside_cnt;
|
||||
else if (result == PointInPolygonResult::IsInside) --outside_cnt;
|
||||
do {
|
||||
switch (PointInOpPolygon(op->pt, op2))
|
||||
{
|
||||
case PointInPolygonResult::IsOutside:
|
||||
if (pip == PointInPolygonResult::IsOutside) return false;
|
||||
pip = PointInPolygonResult::IsOutside;
|
||||
break;
|
||||
case PointInPolygonResult::IsInside:
|
||||
if (pip == PointInPolygonResult::IsInside) return true;
|
||||
pip = PointInPolygonResult::IsInside;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
op = op->next;
|
||||
} while (op != op1 && std::abs(outside_cnt) < 2);
|
||||
if (std::abs(outside_cnt) > 1) return (outside_cnt < 0);
|
||||
// since path1's location is still equivocal, check its midpoint
|
||||
Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint();
|
||||
Path64 path2 = GetCleanPath(op2);
|
||||
return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside;
|
||||
} while (op != op1);
|
||||
// result unclear, so try again using cleaned paths
|
||||
return Path2ContainsPath1(GetCleanPath(op1), GetCleanPath(op2)); // (#973)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void AddLocMin(LocalMinimaList& list,
|
||||
Vertex& vert, PathType polytype, bool is_open)
|
||||
{
|
||||
@@ -1126,21 +1148,19 @@ namespace Clipper2Lib {
|
||||
return newcomer.curr_x > resident.curr_x;
|
||||
|
||||
//get the turning direction a1.top, a2.bot, a2.top
|
||||
double d = CrossProduct(resident.top, newcomer.bot, newcomer.top);
|
||||
if (d != 0) return d < 0;
|
||||
int i = CrossProductSign(resident.top, newcomer.bot, newcomer.top);
|
||||
if (i != 0) return i < 0;
|
||||
|
||||
//edges must be collinear to get here
|
||||
//for starting open paths, place them according to
|
||||
//the direction they're about to turn
|
||||
if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y))
|
||||
{
|
||||
return CrossProduct(newcomer.bot,
|
||||
resident.top, NextVertex(resident)->pt) <= 0;
|
||||
return (CrossProductSign(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0);
|
||||
}
|
||||
else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y))
|
||||
{
|
||||
return CrossProduct(newcomer.bot,
|
||||
newcomer.top, NextVertex(newcomer)->pt) >= 0;
|
||||
return (CrossProductSign(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0);
|
||||
}
|
||||
|
||||
int64_t y = newcomer.bot.y;
|
||||
@@ -1155,7 +1175,7 @@ namespace Clipper2Lib {
|
||||
resident.bot, resident.top)) return true;
|
||||
else
|
||||
//compare turning direction of the alternate bound
|
||||
return (CrossProduct(PrevPrevVertex(resident)->pt,
|
||||
return (CrossProductSign(PrevPrevVertex(resident)->pt,
|
||||
newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft;
|
||||
}
|
||||
|
||||
@@ -1565,7 +1585,7 @@ namespace Clipper2Lib {
|
||||
FixSelfIntersects(outrec);
|
||||
}
|
||||
|
||||
void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp)
|
||||
void ClipperBase::DoSplitOp (OutRec* outrec, OutPt* splitOp)
|
||||
{
|
||||
// splitOp.prev -> splitOp &&
|
||||
// splitOp.next -> splitOp.next.next are intersecting
|
||||
@@ -1574,7 +1594,7 @@ namespace Clipper2Lib {
|
||||
outrec->pts = prevOp;
|
||||
|
||||
Point64 ip;
|
||||
GetSegmentIntersectPt(prevOp->pt, splitOp->pt,
|
||||
GetLineIntersectPt(prevOp->pt, splitOp->pt,
|
||||
splitOp->next->pt, nextNextOp->pt, ip);
|
||||
|
||||
#ifdef USINGZ
|
||||
@@ -1630,7 +1650,7 @@ namespace Clipper2Lib {
|
||||
|
||||
if (using_polytree_)
|
||||
{
|
||||
if (Path1InsidePath2(prevOp, newOp))
|
||||
if (Path2ContainsPath1(prevOp, newOp))
|
||||
{
|
||||
newOr->splits = new OutRecList();
|
||||
newOr->splits->emplace_back(outrec);
|
||||
@@ -1652,19 +1672,32 @@ namespace Clipper2Lib {
|
||||
void ClipperBase::FixSelfIntersects(OutRec* outrec)
|
||||
{
|
||||
OutPt* op2 = outrec->pts;
|
||||
if (op2->prev == op2->next->next)
|
||||
return; // because triangles can't self-intersect
|
||||
for (; ; )
|
||||
{
|
||||
// triangles can't self-intersect
|
||||
if (op2->prev == op2->next->next) break;
|
||||
if (SegmentsIntersect(op2->prev->pt,
|
||||
op2->pt, op2->next->pt, op2->next->next->pt))
|
||||
{
|
||||
if (op2 == outrec->pts || op2->next == outrec->pts)
|
||||
outrec->pts = outrec->pts->prev;
|
||||
DoSplitOp(outrec, op2);
|
||||
if (!outrec->pts) break;
|
||||
op2 = outrec->pts;
|
||||
continue;
|
||||
if (SegmentsIntersect(op2->prev->pt,
|
||||
op2->pt, op2->next->next->pt, op2->next->next->next->pt))
|
||||
{
|
||||
// adjacent intersections (ie a micro self-intersections)
|
||||
op2 = DuplicateOp(op2, false);
|
||||
op2->pt = op2->next->next->next->pt;
|
||||
op2 = op2->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (op2 == outrec->pts || op2->next == outrec->pts)
|
||||
outrec->pts = outrec->pts->prev;
|
||||
DoSplitOp(outrec, op2);
|
||||
if (!outrec->pts) break;
|
||||
op2 = outrec->pts;
|
||||
if (op2->prev == op2->next->next)
|
||||
break; // again, because triangles can't self-intersect
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
op2 = op2->next;
|
||||
@@ -1805,14 +1838,14 @@ namespace Clipper2Lib {
|
||||
|
||||
switch (fillrule_)
|
||||
{
|
||||
case FillRule::Positive:
|
||||
if (edge_c->wind_cnt != 1) return;
|
||||
case FillRule::Positive:
|
||||
if (edge_c->wind_cnt != 1) return;
|
||||
break;
|
||||
case FillRule::Negative:
|
||||
if (edge_c->wind_cnt != -1) return;
|
||||
case FillRule::Negative:
|
||||
if (edge_c->wind_cnt != -1) return;
|
||||
break;
|
||||
default:
|
||||
if (std::abs(edge_c->wind_cnt) != 1) return;
|
||||
default:
|
||||
if (std::abs(edge_c->wind_cnt) != 1) return;
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
@@ -1933,7 +1966,7 @@ namespace Clipper2Lib {
|
||||
const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1;
|
||||
const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1;
|
||||
|
||||
if ((!IsHotEdge(e1) && !e1_windcnt_in_01) ||
|
||||
if ((!IsHotEdge(e1) && !e1_windcnt_in_01) ||
|
||||
(!IsHotEdge(e2) && !e2_windcnt_in_01))
|
||||
return;
|
||||
|
||||
@@ -2112,10 +2145,9 @@ namespace Clipper2Lib {
|
||||
e->prev_in_sel = e->prev_in_ael;
|
||||
e->next_in_sel = e->next_in_ael;
|
||||
e->jump = e->next_in_sel;
|
||||
if (e->join_with == JoinWith::Left)
|
||||
e->curr_x = e->prev_in_ael->curr_x; // also avoids complications
|
||||
else
|
||||
e->curr_x = TopX(*e, top_y);
|
||||
// it is safe to ignore 'joined' edges here because
|
||||
// if necessary they will be split in IntersectEdges()
|
||||
e->curr_x = TopX(*e, top_y);
|
||||
e = e->next_in_ael;
|
||||
}
|
||||
}
|
||||
@@ -2262,15 +2294,14 @@ namespace Clipper2Lib {
|
||||
|
||||
void MoveSplits(OutRec* fromOr, OutRec* toOr)
|
||||
{
|
||||
if (!fromOr->splits) return;
|
||||
if (!toOr->splits) toOr->splits = new OutRecList();
|
||||
OutRecList::iterator orIter = fromOr->splits->begin();
|
||||
for (; orIter != fromOr->splits->end(); ++orIter)
|
||||
toOr->splits->emplace_back(*orIter);
|
||||
if (toOr != *orIter) // #987
|
||||
toOr->splits->emplace_back(*orIter);
|
||||
fromOr->splits->clear();
|
||||
}
|
||||
|
||||
|
||||
void ClipperBase::ProcessHorzJoins()
|
||||
{
|
||||
for (const HorzJoin& j : horz_join_list_)
|
||||
@@ -2299,8 +2330,8 @@ namespace Clipper2Lib {
|
||||
}
|
||||
|
||||
if (using_polytree_) //#498, #520, #584, D#576, #618
|
||||
{
|
||||
if (Path1InsidePath2(or1->pts, or2->pts))
|
||||
{
|
||||
if (Path2ContainsPath1(or1->pts, or2->pts))
|
||||
{
|
||||
//swap or1's & or2's pts
|
||||
OutPt* tmp = or1->pts;
|
||||
@@ -2311,7 +2342,7 @@ namespace Clipper2Lib {
|
||||
//or2 is now inside or1
|
||||
or2->owner = or1;
|
||||
}
|
||||
else if (Path1InsidePath2(or2->pts, or1->pts))
|
||||
else if (Path2ContainsPath1(or2->pts, or1->pts))
|
||||
{
|
||||
or2->owner = or1;
|
||||
}
|
||||
@@ -2324,13 +2355,14 @@ namespace Clipper2Lib {
|
||||
else
|
||||
or2->owner = or1;
|
||||
}
|
||||
else
|
||||
else // joining, not splitting
|
||||
{
|
||||
or2->pts = nullptr;
|
||||
if (using_polytree_)
|
||||
{
|
||||
SetOwner(or2, or1);
|
||||
MoveSplits(or2, or1); //#618
|
||||
if (or2->splits)
|
||||
MoveSplits(or2, or1); //#618
|
||||
}
|
||||
else
|
||||
or2->owner = or1;
|
||||
@@ -2350,7 +2382,7 @@ namespace Clipper2Lib {
|
||||
void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)
|
||||
{
|
||||
Point64 ip;
|
||||
if (!GetSegmentIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))
|
||||
if (!GetLineIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))
|
||||
ip = Point64(e1.curr_x, top_y); //parallel edges
|
||||
|
||||
//rounding errors can occasionally place the calculated intersection
|
||||
@@ -2934,22 +2966,28 @@ namespace Clipper2Lib {
|
||||
|
||||
bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits)
|
||||
{
|
||||
for (auto split : *splits)
|
||||
// nb: use indexing (not an iterator) in case 'splits' is modified inside this loop (#1029)
|
||||
for (size_t idx = 0; idx < splits->size(); ++idx)
|
||||
{
|
||||
OutRec* split = (*splits)[idx];
|
||||
if (!split->pts && split->splits &&
|
||||
CheckSplitOwner(outrec, split->splits)) return true; //#942
|
||||
split = GetRealOutRec(split);
|
||||
if(!split || split == outrec || split->recursive_split == outrec) continue;
|
||||
if (!split || split == outrec || split->recursive_split == outrec) continue;
|
||||
split->recursive_split = outrec; // prevent infinite loops
|
||||
|
||||
if (split->splits && CheckSplitOwner(outrec, split->splits))
|
||||
return true;
|
||||
else if (CheckBounds(split) &&
|
||||
IsValidOwner(outrec, split) &&
|
||||
split->bounds.Contains(outrec->bounds) &&
|
||||
Path1InsidePath2(outrec->pts, split->pts))
|
||||
{
|
||||
outrec->owner = split; //found in split
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
|
||||
if (!CheckBounds(split) || !split->bounds.Contains(outrec->bounds) ||
|
||||
!Path2ContainsPath1(outrec->pts, split->pts)) continue;
|
||||
|
||||
if (!IsValidOwner(outrec, split)) // split is owned by outrec! (#957)
|
||||
split->owner = outrec->owner;
|
||||
|
||||
outrec->owner = split;
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -2960,13 +2998,12 @@ namespace Clipper2Lib {
|
||||
// post-condition: if a valid path, outrec will have a polypath
|
||||
|
||||
if (outrec->polypath || outrec->bounds.IsEmpty()) return;
|
||||
|
||||
while (outrec->owner)
|
||||
{
|
||||
if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break;
|
||||
if (outrec->owner->pts && CheckBounds(outrec->owner) &&
|
||||
outrec->owner->bounds.Contains(outrec->bounds) &&
|
||||
Path1InsidePath2(outrec->pts, outrec->owner->pts)) break;
|
||||
Path2ContainsPath1(outrec->pts, outrec->owner->pts)) break;
|
||||
outrec->owner = outrec->owner->owner;
|
||||
}
|
||||
|
||||
@@ -3029,6 +3066,7 @@ namespace Clipper2Lib {
|
||||
{
|
||||
OutRec* outrec = outrec_list_[i];
|
||||
if (!outrec || !outrec->pts) continue;
|
||||
|
||||
if (outrec->is_open)
|
||||
{
|
||||
Path64 path;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 22 January 2025 *
|
||||
* Date : 11 October 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Path Offset (Inflate/Shrink) *
|
||||
@@ -37,29 +37,35 @@ const double arc_const = 0.002; // <-- 1/500
|
||||
// Miscellaneous methods
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths)
|
||||
void GetLowestClosedPathInfo(const Paths64& paths, std::optional<size_t>& idx, bool& is_neg_area)
|
||||
{
|
||||
std::optional<size_t> result;
|
||||
idx.reset();
|
||||
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
|
||||
for (size_t i = 0; i < paths.size(); ++i)
|
||||
{
|
||||
double a = MAX_DBL;
|
||||
for (const Point64& pt : paths[i])
|
||||
{
|
||||
if ((pt.y < botPt.y) ||
|
||||
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
|
||||
result = i;
|
||||
if (a == MAX_DBL)
|
||||
{
|
||||
a = Area(paths[i]);
|
||||
if (a == 0) break; // invalid closed path, so break from inner loop
|
||||
is_neg_area = a < 0;
|
||||
}
|
||||
idx = i;
|
||||
botPt.x = pt.x;
|
||||
botPt.y = pt.y;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline double Hypot(double x, double y)
|
||||
{
|
||||
// given that this is an internal function, and given the x and y parameters
|
||||
// will always be coordinate values (or the difference between coordinate values),
|
||||
// x and y should always be within INT64_MIN to INT64_MAX. Consequently,
|
||||
// x and y should always be within INT64_MIN to INT64_MAX. Consequently,
|
||||
// there should be no risk that the following computation will overflow
|
||||
// see https://stackoverflow.com/a/32436148/359538
|
||||
return std::sqrt(x * x + y * y);
|
||||
@@ -145,15 +151,16 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType
|
||||
|
||||
if (end_type == EndType::Polygon)
|
||||
{
|
||||
lowest_path_idx = GetLowestClosedPathIdx(paths_in);
|
||||
bool is_neg_area;
|
||||
GetLowestClosedPathInfo(paths_in, lowest_path_idx, is_neg_area);
|
||||
// the lowermost path must be an outer path, so if its orientation is negative,
|
||||
// then flag the whole group is 'reversed' (will negate delta etc.)
|
||||
// as this is much more efficient than reversing every path.
|
||||
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0;
|
||||
is_reversed = lowest_path_idx.has_value() && is_neg_area;
|
||||
}
|
||||
else
|
||||
{
|
||||
lowest_path_idx = std::nullopt;
|
||||
lowest_path_idx.reset();
|
||||
is_reversed = false;
|
||||
}
|
||||
}
|
||||
@@ -236,7 +243,7 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
//get the second intersect point through reflecion
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
path_out.emplace_back(pt);
|
||||
@@ -245,7 +252,7 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
path_out.emplace_back(pt);
|
||||
//get the second intersect point through reflecion
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
@@ -291,7 +298,8 @@ void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle
|
||||
#else
|
||||
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
|
||||
#endif
|
||||
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
|
||||
// Orca: round the step count like Clipper1 did, so round offsets keep their vertices.
|
||||
int steps = std::max(static_cast<int>(std::round(steps_per_rad_ * std::abs(angle))), 1);
|
||||
for (int i = 1; i < steps; ++i) // ie 1 less than steps
|
||||
{
|
||||
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
|
||||
@@ -333,9 +341,9 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size
|
||||
if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
|
||||
{
|
||||
// is concave
|
||||
// by far the simplest way to construct concave joins, especially those joining very
|
||||
// short segments, is to insert 3 points that produce negative regions. These regions
|
||||
// will be removed later by the finishing union operation. This is also the best way
|
||||
// by far the simplest way to construct concave joins, especially those joining very
|
||||
// short segments, is to insert 3 points that produce negative regions. These regions
|
||||
// will be removed later by the finishing union operation. This is also the best way
|
||||
// to ensure that path reversals (ie over-shrunk paths) are removed.
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_), path[j].z);
|
||||
@@ -366,11 +374,31 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size
|
||||
DoSquare(path, j, k);
|
||||
}
|
||||
|
||||
// Orca: join concave corners at the crossing of both edge offsets where safe, 3-point loops make dense inward offsets slow.
|
||||
static bool OffsetConcaveCrossing(const Path64& path, const PathD& norms, size_t j, size_t k, size_t next,
|
||||
double delta, Path64& path_out)
|
||||
{
|
||||
const double sin_a = CrossProduct(norms[j], norms[k]);
|
||||
const double cos_a = DotProduct(norms[j], norms[k]);
|
||||
if (cos_a <= -0.999 || sin_a * delta >= 0) return false;
|
||||
const double x = std::fabs(delta * sin_a) / (1 + cos_a);
|
||||
if (4 * x * x > DistanceSqr(path[k], path[j]) || 4 * x * x > DistanceSqr(path[j], path[next])) return false;
|
||||
const double q = delta / (1 + cos_a);
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(path[j].x + (norms[k].x + norms[j].x) * q, path[j].y + (norms[k].y + norms[j].y) * q, path[j].z);
|
||||
#else
|
||||
path_out.emplace_back(path[j].x + (norms[k].x + norms[j].x) * q, path[j].y + (norms[k].y + norms[j].y) * q);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
|
||||
{
|
||||
path_out.clear();
|
||||
for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
|
||||
OffsetPoint(group, path, j, k);
|
||||
if (deltaCallback64_ || path[j] == path[k] ||
|
||||
!OffsetConcaveCrossing(path, norms, j, k, j + 1 == path.size() ? 0 : j + 1, group_delta_, path_out))
|
||||
OffsetPoint(group, path, j, k);
|
||||
solution->emplace_back(path_out);
|
||||
}
|
||||
|
||||
@@ -380,7 +408,7 @@ void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
|
||||
Path64 reverse_path(path);
|
||||
std::reverse(reverse_path.begin(), reverse_path.end());
|
||||
|
||||
//rebuild normals
|
||||
//rebuild normals
|
||||
std::reverse(norms.begin(), norms.end());
|
||||
norms.emplace_back(norms[0]);
|
||||
norms.erase(norms.begin());
|
||||
@@ -601,10 +629,10 @@ void ClipperOffset::ExecuteInternal(double delta)
|
||||
|
||||
if (!solution->size()) return;
|
||||
|
||||
bool paths_reversed = CheckReverseOrientation();
|
||||
bool paths_reversed = CheckReverseOrientation();
|
||||
//clean up self-intersections ...
|
||||
Clipper64 c;
|
||||
c.PreserveCollinear(false);
|
||||
c.PreserveCollinear(preserve_collinear_);
|
||||
//the solution should retain the orientation of the input
|
||||
c.ReverseSolution(reverse_solution_ != paths_reversed);
|
||||
#ifdef USINGZ
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 5 July 2024 *
|
||||
* Date : 11 October 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : FAST rectangular clipping *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -77,8 +77,8 @@ namespace Clipper2Lib {
|
||||
bool GetSegmentIntersection(const Point64& p1,
|
||||
const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip)
|
||||
{
|
||||
double res1 = CrossProduct(p1, p3, p4);
|
||||
double res2 = CrossProduct(p2, p3, p4);
|
||||
int res1 = CrossProductSign(p1, p3, p4);
|
||||
int res2 = CrossProductSign(p2, p3, p4);
|
||||
if (res1 == 0)
|
||||
{
|
||||
ip = p1;
|
||||
@@ -97,8 +97,8 @@ namespace Clipper2Lib {
|
||||
}
|
||||
if ((res1 > 0) == (res2 > 0)) return false;
|
||||
|
||||
double res3 = CrossProduct(p3, p1, p2);
|
||||
double res4 = CrossProduct(p4, p1, p2);
|
||||
int res3 = CrossProductSign(p3, p1, p2);
|
||||
int res4 = CrossProductSign(p4, p1, p2);
|
||||
if (res3 == 0)
|
||||
{
|
||||
ip = p3;
|
||||
@@ -116,7 +116,7 @@ namespace Clipper2Lib {
|
||||
if ((res3 > 0) == (res4 > 0)) return false;
|
||||
|
||||
// segments must intersect to get here
|
||||
return GetSegmentIntersectPt(p1, p2, p3, p4, ip);
|
||||
return GetLineIntersectPt(p1, p2, p3, p4, ip);
|
||||
}
|
||||
|
||||
inline bool GetIntersection(const Path64& rectPath,
|
||||
@@ -227,7 +227,7 @@ namespace Clipper2Lib {
|
||||
const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp)
|
||||
{
|
||||
if (AreOpposites(prev, curr))
|
||||
return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
|
||||
return CrossProductSign(prev_pt, rect_mp, curr_pt) < 0;
|
||||
else
|
||||
return HeadingClockwise(prev, curr);
|
||||
}
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
#include "clipper.engine.cpp"
|
||||
#include "clipper.offset.cpp"
|
||||
#include "clipper.rectclip.cpp"
|
||||
#include "clipper.triangulation.cpp"
|
||||
|
||||
@@ -132,14 +132,14 @@ template<>
|
||||
inline void offset(Slic3r::ExPolygon& sh, coord_t distance, const PolygonTag&)
|
||||
{
|
||||
#define DISABLE_BOOST_OFFSET
|
||||
auto res = Slic3r::offset_ex(sh, distance, Slic3r::ClipperLib::jtSquare);
|
||||
auto res = Slic3r::offset_ex(sh, distance, Slic3r::jtSquare);
|
||||
if (!res.empty()) sh = res.front();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void offset(Slic3r::Polygon& sh, coord_t distance, const PathTag&)
|
||||
{
|
||||
auto res = Slic3r::offset(sh, distance, Slic3r::ClipperLib::jtSquare);
|
||||
auto res = Slic3r::offset(sh, distance, Slic3r::jtSquare);
|
||||
if (!res.empty()) sh = res.front();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,7 @@ if(Qhull_FOUND)
|
||||
message(STATUS "Using qhull from system.")
|
||||
if(SLIC3R_STATIC)
|
||||
slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhullstatic_r" RelWithDebInfo Release)
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
target_link_libraries(qhull INTERFACE Qhull::qhullcpp_d Qhull::qhullstatic_rd)
|
||||
else()
|
||||
target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r)
|
||||
endif()
|
||||
target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r)
|
||||
else()
|
||||
slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhull_r" RelWithDebInfo Release)
|
||||
target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhull_r)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# Polygon Clipping — High Level Design
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
Almost every stage of slicing works on 2D regions: slices, perimeters, infill
|
||||
areas, bridges, supports and brims are all produced by boolean operations and
|
||||
offsets on polygons. libslic3r does this through two interfaces, both built on
|
||||
the Clipper2 library vendored in `deps_src/clipper2`:
|
||||
|
||||
- `ClipperUtils` (`src/libslic3r/ClipperUtils.hpp`) takes and returns Slic3r
|
||||
geometry: `Polygon(s)`, `ExPolygon(s)`, `Polyline(s)`, `Lines` and
|
||||
`Surfaces`. It provides unions, intersections, differences and xor, closed
|
||||
and open offsets, morphological opening and closing, variable width offsets
|
||||
and polyline clipping.
|
||||
- `ClipperZUtils` (`src/libslic3r/ClipperZUtils.hpp`) clips paths whose
|
||||
vertices carry a Z value, which callers use to tag vertices with a source
|
||||
index or an extrusion width.
|
||||
|
||||
No other code calls Clipper2.
|
||||
|
||||
`ClipperUtils` declares its own `JoinType`, `EndType`, `PolyFillType` and
|
||||
`ClipType` enums and maps them to Clipper2's. Every call builds its own
|
||||
Clipper2 objects and shares no state, so slicing threads can clip
|
||||
concurrently.
|
||||
|
||||
Clipping is one of the largest costs of slicing, and nearly all of it goes
|
||||
through `ClipperUtils`. The layer is therefore designed for throughput as much
|
||||
as for predictable geometry.
|
||||
|
||||
## Vendored Clipper2
|
||||
|
||||
`deps_src/clipper2` builds the static target `Clipper2`. It carries four
|
||||
changes to the upstream sources that must be carried over when Clipper2 is
|
||||
updated. The namespace switch sits at the top of every header and source, the
|
||||
other three are marked with `Orca:` comments.
|
||||
|
||||
| Change | Files | Why |
|
||||
| --- | --- | --- |
|
||||
| Z build in its own namespace | all headers and sources, `clipper2_z.cpp`, `clipper2_z.hpp` | The library is compiled a second time with `USINGZ` in namespace `Clipper2Lib_Z`, so the 2D and the Z variants link into one binary. |
|
||||
| Engine nodes from tbbmalloc | `clipper.engine.h`, `clipper.engine.cpp` | Vertices, active edges, output points and records, local minima and `PolyTree` nodes are allocated one by one. `CLIPPER2_NODE_ALLOCATOR` routes them through `scalable_malloc`, because the default heap does not scale when all slicing threads clip at once. |
|
||||
| Concave joins at the edge crossing | `clipper.offset.cpp` | For closed paths, a concave corner is joined at the crossing of the two offset edges when that point lies within half of both adjacent edges. The upstream 3-point loop makes inward offsets of dense curves very slow to union. |
|
||||
| Rounded arc steps | `clipper.offset.cpp` | Round joins use the rounded number of steps, not the ceiling, which keeps the vertex count of round offsets that the rest of the code is tuned for. |
|
||||
|
||||
## ClipperUtils semantics
|
||||
|
||||
The callers of `ClipperUtils` rely on a fixed set of behaviours. Where
|
||||
Clipper2 behaves differently by default, the wrapper adjusts it.
|
||||
|
||||
### Booleans
|
||||
|
||||
- The fill rule is non-zero unless the function takes a `PolyFillType`. One
|
||||
rule applies to both subject and clip; Clipper2 has no per-operand rule.
|
||||
- Collinear vertices are removed from the result. Clipper2 keeps them by
|
||||
default, so every boolean sets `PreserveCollinear(false)`.
|
||||
- Outer contours are CCW and holes are CW. No output contour touches
|
||||
itself: where one would pass twice through a vertex, it is split there into
|
||||
two contours.
|
||||
- `ExPolygons` results are built from one `PolyTree64` pass. An island inside
|
||||
a hole becomes an `ExPolygon` of its own.
|
||||
- `ApplySafetyOffset::Yes` grows the clip polygons by `ClipperSafetyOffset`
|
||||
before an intersection or a difference, so that edges shared by subject and
|
||||
clip do not leave slivers.
|
||||
- Open polylines are clipped with the non-zero rule and keep their direction.
|
||||
|
||||
### Offsets
|
||||
|
||||
- Before offsetting, input vertices closer than
|
||||
`ClipperOffsetShortestEdgeFactor` × |delta| to the previously kept vertex
|
||||
are dropped. This bounds the work on dense contours, and the error it
|
||||
introduces is far below the offset distance.
|
||||
- The miter limit is at least 2. For `jtRound`, a positive `miterLimit`
|
||||
argument is the arc tolerance, capped at |delta| / 4, and 0.25 is used
|
||||
otherwise. Other joins use the smaller of 0.25 and |delta| / 4 for round end
|
||||
caps.
|
||||
- A single `Polygon` keeps its orientation: a CCW polygon grows with a
|
||||
positive delta, a CW polygon is a hole and shrinks.
|
||||
- `Polygons` follow the same rule per path. When every CW path lies strictly
|
||||
inside the bounding box of a CCW path, which is the usual case of contours
|
||||
with their holes, all paths are offset in one Clipper2 group. Otherwise
|
||||
each path is offset on its own and the results are united, with the
|
||||
non-zero rule when growing and the positive rule when shrinking.
|
||||
- `ExPolygons` and `Surfaces` are offset as one group after the contours are
|
||||
oriented CCW and the holes CW, whatever their input orientation.
|
||||
- Zero-area paths vanish under a negative offset instead of growing.
|
||||
- Polyline offsets use the requested end type. Clipper2 already unites the
|
||||
result, so no further union is done.
|
||||
|
||||
### Coordinate range
|
||||
|
||||
Clipper2 computes intersections and slopes in doubles, which hold integers
|
||||
exactly only up to 2^53 (about 9e15 units, 9,000 km). Geometry passed to
|
||||
`ClipperUtils` must stay well inside that range; near the int64 limit the
|
||||
results shift by hundreds of units. This is why the arrange `InfiniteBed` is a
|
||||
box of ±2^50 units around its centre rather than libnest2d's infinite box,
|
||||
which reaches ±2.3e18.
|
||||
|
||||
## ClipperZUtils
|
||||
|
||||
`ZPoint` is a `Vec3crd`, and a `ZPath` is a vector of them.
|
||||
`clip_zpaths()` runs one boolean with the non-zero rule on the Clipper2 Z
|
||||
build. The subject may be open, the clip is closed, and the result lists the
|
||||
closed paths before the open ones.
|
||||
|
||||
The Z of each output vertex follows these rules:
|
||||
|
||||
- An input vertex keeps its Z.
|
||||
- An intersection that lies on an end point of one of the two crossing edges
|
||||
takes that end point's Z, preferring the subject edge.
|
||||
- Any other intersection gets its Z from the callback, which receives both
|
||||
crossing edges, the subject edge first.
|
||||
|
||||
Clipper2 calls the callback only when it creates an output vertex at an
|
||||
intersection, not for every crossing it processes. A callback that records
|
||||
intersections, like `ClipperZIntersectionVisitor`, therefore sees only those.
|
||||
|
||||
The users are:
|
||||
|
||||
| User | Z carries |
|
||||
| --- | --- |
|
||||
| `Algorithm::wave_seeds()` (region expansion) | source and boundary index; intersections get a negative index into the visitor's list of crossing pairs |
|
||||
| `Algorithm::split_line()` | index of the source vertex; an intersection gets the negated index of its source edge, so the pieces can be put back in path order |
|
||||
| `PerimeterGenerator` overhang and top-surface clipping of Arachne walls | extrusion width, interpolated along the edge at intersections |
|
||||
| Tree support anchors in `SupportCommon` | index of the source contour, -1 at intersections |
|
||||
| `extrusion_paths_append()` | extrusion width, turned into extrusion paths |
|
||||
|
||||
## Testing
|
||||
|
||||
`tests/libslic3r/test_clipper_utils.cpp` and `test_clipper_offset.cpp` cover
|
||||
the wrapper's booleans, orientation and offset rules. The perimeter, support
|
||||
and region expansion users are exercised by the slicing tests in
|
||||
`tests/fff_print`.
|
||||
@@ -0,0 +1,93 @@
|
||||
# 3D Scene Benchmark: High Level Design
|
||||
|
||||
## Why it exists
|
||||
|
||||
Rendering changes, such as the realistic view, shadows or SSAO, need a number to compare
|
||||
before and after, and user reports of a slow viewport need a way to say how slow. The FPS
|
||||
overlay and the render timings overlay show live values while someone drags the camera,
|
||||
which varies from run to run with the model, the path of the mouse and the view.
|
||||
|
||||
The benchmark renders a fixed model along a fixed camera path in both 3D views, so two
|
||||
runs on the same machine differ only by the code or the settings, and prints a report that
|
||||
can be pasted into an issue.
|
||||
|
||||
## What it does
|
||||
|
||||
`run_scene_benchmark()` in `src/slic3r/GUI/SceneBenchmark.cpp` is reached from Help >
|
||||
Benchmark 3D Scene, the command palette and Preferences > Graphics. After a confirmation
|
||||
it starts a new project, which asks to save the current one if needed, loads the
|
||||
OrcaSliced Combo handy model and arranges it. A small dialog in a corner of the 3D view
|
||||
then shows the progress; every other window is disabled until the run ends, so a click
|
||||
cannot change the scene being measured. Cancel or Esc stops the run.
|
||||
|
||||
The run goes through these stages, driven by a timer while it waits and by idle events
|
||||
while it renders:
|
||||
|
||||
1. Loading: waits until the UI job worker is idle, so the arrange job has moved the
|
||||
objects. The orbit target is the center of the objects on the current plate, and the
|
||||
base zoom fits their bounding box in the viewport.
|
||||
2. Prepare: renders the scene in the Prepare view.
|
||||
3. Slicing: slices the plate and switches to Preview, then waits for the G-code preview
|
||||
to load. If slicing fails, the report holds Prepare alone.
|
||||
4. Preview: renders the scene in the Preview view, with the slicing progress notification
|
||||
hidden.
|
||||
5. Layers: renders the Preview view again while the layer slider moves, which is what
|
||||
makes dragging it feel slow on large prints.
|
||||
|
||||
The dialog then shows the report, with a button to copy it. A scene cut short, because its
|
||||
view was hidden, is left out of the report.
|
||||
|
||||
## Rendering a scene
|
||||
|
||||
Each scene renders 30 warm-up frames, then the camera path twice, 360 frames each time.
|
||||
|
||||
- The first pass times the frames. A frame's time is the interval between the starts of
|
||||
consecutive benchmark frames, so it includes the event loop between them.
|
||||
- The second pass averages the render timings. The frame profiler flushes the GL command
|
||||
queue after each section, which slows a frame down, so it only runs in this pass.
|
||||
`FrameProfiler::start_averaging()` flags every frame begun afterwards, and
|
||||
`finish_averaging()` waits for the flagged frames still on the GPU and returns the mean
|
||||
CPU and GPU time of each section per profiled frame.
|
||||
- A section's GPU time is taken between a timestamp before its commands and one after
|
||||
them. The first is only sent along with those commands, so when the GPU finishes a
|
||||
section before the CPU has issued the next one, the wait counts in neither.
|
||||
|
||||
The dialog renders one frame per idle event by calling `GLCanvas3D::render()`, which
|
||||
redraws the whole scene. While `GLCanvas3D::set_benchmarking()` is on, the canvas does not
|
||||
render from its own idle handler, so no other frame is drawn in between, and it skips the
|
||||
picking pass and the FPS and render timings overlays, which depend on the mouse and on
|
||||
preferences. The FPS cap does not apply, since it only paces idle redraws.
|
||||
|
||||
VSync is turned off for the scene through `wxGLCanvas::SetSwapInterval(0)`, so the frame
|
||||
rate is what the GPU and CPU can reach rather than the display's refresh rate, and the
|
||||
previous interval is restored afterwards. When the platform cannot report the current
|
||||
interval (EGL), it is left as it is and the report says so.
|
||||
|
||||
The camera path makes two turns around the target while the view rises three times from
|
||||
25 degrees below the plate to 85 degrees above it and the zoom goes twice between 0.6 and
|
||||
1.4 times the base zoom. The camera stays at the default distance, so the perspective is
|
||||
the same in every run. The camera the scene started with is restored at its end.
|
||||
|
||||
The Layers scene holds the camera at the start of that path and moves the top of the layer
|
||||
slider instead, from the last layer down to the first and back up in each pass. It goes
|
||||
through `IMSlider::SetHigherValue()`, as a drag does, so every frame applies a new layer
|
||||
range to the toolpaths and the objects before drawing them, including a new shadow map when
|
||||
the shadows are static. Its warm-up frames lead into the start of the path, so the slider
|
||||
moves in every frame. The slider position it started from is restored at its end.
|
||||
|
||||
## The report
|
||||
|
||||
The report is plain English text, so it reads the same in every language:
|
||||
|
||||
- The version and build commit, the GPU and OpenGL version, the viewport size and camera
|
||||
type, and the graphics settings that change the cost of a frame: MSAA samples as read
|
||||
from the framebuffer, FXAA, the scene cache, VSync and the realistic view options.
|
||||
- The printer and process presets the model was sliced with, marked when they have
|
||||
unsaved changes, and the toolpath vertices and layers they produced, since the Preview
|
||||
scenes cost more with more toolpaths.
|
||||
- For each scene, the average FPS and the average, median, 95th percentile, 99th
|
||||
percentile and maximum frame time. Percentiles are nearest-rank, so each is a measured
|
||||
frame (`frame_time_stats()`).
|
||||
- For each scene, the render timings table: the CPU and GPU milliseconds of each section
|
||||
of a frame, and their total. Without timer queries (OpenGL 3.3 or `ARB_timer_query`) the
|
||||
table says that the driver does not support them.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Section view — High Level Design
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
Section view hides whatever lies between the camera and a plane, so the user can look inside
|
||||
objects in Prepare and in the assembly view, and inside the toolpaths in Preview. It is a view
|
||||
setting: it changes nothing in the model, the slice or the project file, and it does not reach
|
||||
plate thumbnails.
|
||||
|
||||
The user controls it from the section button of the canvas toolbar in the bottom left corner of
|
||||
the 3D view. The button opens a panel above it with a slider for the depth of the cut, a "Set
|
||||
viewing angle" button that turns the plane to face the camera at the same depth, and a button that
|
||||
resets the depth to zero. The panel is an ordinary overlay window, not a popup, so the scene keeps
|
||||
taking clicks and drags while it is open; the button or Esc closes it again. Esc closes the panel
|
||||
before it closes a gizmo or clears the selection. The button is highlighted
|
||||
while a section cuts the scene and has shortcuts of its own: the mouse wheel over it moves the
|
||||
plane, a right click switches the section off and back on, and a middle click sets the viewing
|
||||
angle. Alt + mouse wheel moves the plane anywhere in the 3D view, with or without a gizmo open.
|
||||
|
||||
## State
|
||||
|
||||
Prepare and Preview share one section view, so a cut made in either tab is the same cut in the
|
||||
other. The assembly view, whose objects sit apart from their places on the plate, and the Design
|
||||
tab keep their own. The section itself is two values.
|
||||
|
||||
- **Ratio**, from 0 to 1. At 0 the section is off. As the ratio grows, the plane sweeps the
|
||||
sphere around the objects, from its side facing the camera to the opposite side, so at 1
|
||||
everything is cut away.
|
||||
- **Normal**, taken from the camera direction the first time the section is switched on, and
|
||||
again whenever the user sets the viewing angle. The plane keeps that orientation while the
|
||||
camera orbits and while the section is off, so the cut face can be seen from any side and
|
||||
bringing the depth back to 0 does not lose the angle.
|
||||
|
||||
The ratio in use when the section is switched off is kept, and the right click on the button
|
||||
brings the section back at that ratio, which restores the same cut. Whether the panel is open is
|
||||
shared along with the section.
|
||||
|
||||
The sphere is recomputed every frame from the volumes of the canvas the section view belongs to:
|
||||
the objects on the current plate, or every object when that plate is empty. Preview holds no
|
||||
objects of its own, so it places the plane across the volumes of Prepare, which makes it cut the
|
||||
toolpaths exactly where Prepare cuts the objects. Only G-code opened on its own, without objects,
|
||||
is measured by its toolpaths. In the assembly view the sphere is around the whole assembly. The
|
||||
ratio therefore keeps its meaning when objects move or the user switches plates. It is not a
|
||||
fixed position in world space.
|
||||
|
||||
Only the tab on screen can change the section, and switching tabs redraws the whole scene and
|
||||
closes the open gizmo, so neither tab ever shows a stale cut.
|
||||
|
||||
## Where the plane applies
|
||||
|
||||
`GLCanvas3D::_get_section_view_plane()` turns the state into a plane in the convention of
|
||||
`ClippingPlane::is_point_clipped()`. Everything that draws or picks the scene reads that plane.
|
||||
|
||||
- **Volumes.** The plane goes to the `clipping_plane` uniform of the volume shaders. The same
|
||||
uniform serves the gouraud, phong and X-ray passes and the colour picking pass.
|
||||
- **Cut faces.** Clipping only discards fragments, which would leave the cut volumes hollow.
|
||||
`_render_section_view_caps()` draws their cut faces with one `MeshClipper` per model part the
|
||||
plane passes through. A clipper recomputes its face only when the plane or the volume moves.
|
||||
Modifiers, the wipe tower and SLA auxiliaries get no face.
|
||||
- **Toolpaths.** libvgcode takes the plane through `Viewer::set_clipping_plane()`. It draws each
|
||||
extrusion as only the faces of a diamond-section prism that turn towards the camera, so
|
||||
discarding the fragments on the clipped side would leave open shells. Instead, the segment
|
||||
shader follows the view ray from a fragment that is cut away to the plane. When the
|
||||
extrusion's diamond section still holds that point, the fragment is shaded as the cut face, lit
|
||||
as the plane faces; otherwise it is discarded. The cut face keeps the depth of the fragment it
|
||||
replaces, which is safe: along that ray everything else still shown lies behind the plane. The
|
||||
shader writes no `gl_FragDepth`, so early depth testing survives. Option markers are cut away
|
||||
whole, by their centres. The shadow casters draw with a program of their own, which takes the
|
||||
plane and discards the fragments on the clipped side, so what is cut away casts no shadow either.
|
||||
Their cut faces are not drawn, since the part left behind casts the shadow of its own section.
|
||||
Preview shells are drawn by another shader and are not clipped.
|
||||
- **Picking.** `get_raycaster_clipping_plane()` returns the same plane, so hover, selection and
|
||||
the perspective pan anchor ignore what the user cannot see.
|
||||
|
||||
## Gizmos
|
||||
|
||||
A gizmo that clips its object itself owns the gizmo data pool's `ObjectClipper`, and its plane
|
||||
replaces the canvas section while the gizmo is open. `GLGizmosManager::get_clipping_plane()`
|
||||
reports that plane, or nothing when no open gizmo has a clipper. There are two cases.
|
||||
|
||||
- **Painting tools and brim ears** show the canvas section on the object they edit.
|
||||
`GLGizmosManager::update_section_view()` copies the ratio and normal into their clipper
|
||||
whenever the pool is updated or the section changes. The clipper then places the plane across
|
||||
the edited instance, which is the only object shown. The painting tools keep clipping their
|
||||
own triangles, raycasts and cut face through it. Brim ears always cut horizontally from the
|
||||
top, because the ears sit on the plate. At ratio 0 the clipper holds no plane at all, so the
|
||||
raycasts are not clipped.
|
||||
- **Cut and mesh boolean** use the clipper for their own purposes, so the canvas section is
|
||||
suspended while they are open.
|
||||
|
||||
Every other gizmo, including move, rotate and scale, leaves the canvas section in place.
|
||||
|
||||
## Alt + mouse wheel
|
||||
|
||||
The canvas handles Alt + wheel after the gizmos had their turn, so it works the same in every
|
||||
tab and with any gizmo open. On Windows, releasing Alt when no key was pressed since it went down
|
||||
opens the window menu, and a wheel turn does not count as a key. Under the custom title bar that
|
||||
menu is invisible, yet it takes the keyboard and the next click, which looks like a frozen 3D
|
||||
view. After Alt + wheel the canvas therefore consumes the Alt release instead of passing it on.
|
||||
|
||||
## Redraw
|
||||
|
||||
The button and the panel are part of the ImGui overlay, which is built after the frame's scene is
|
||||
drawn. A change to the section therefore marks the scene dirty and asks for one more frame. The
|
||||
cached scene is never reused across a change.
|
||||
@@ -180,6 +180,7 @@ src/slic3r/GUI/PrivacyUpdateDialog.cpp
|
||||
src/slic3r/GUI/PublishDialog.cpp
|
||||
src/slic3r/GUI/PublishSettingsDialog.cpp
|
||||
src/slic3r/GUI/SavePresetDialog.cpp
|
||||
src/slic3r/GUI/SceneBenchmark.cpp
|
||||
src/slic3r/GUI/Search.cpp
|
||||
src/slic3r/GUI/SettingsIndex.cpp
|
||||
src/slic3r/GUI/SpeedDialDialog.cpp
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#fafafa;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#2b3436; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#009688;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#00675b;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#008172;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#26a69a;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#393c42;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#b6b6b6; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#283232;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#b6b6b6; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#e5f0ee;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#2b3436; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -63,6 +63,10 @@ uniform float shadow_map_texel;
|
||||
|
||||
// LIGHT_TOP_DIR in eye space (matches the diffuse light used for shading in gouraud.vs).
|
||||
const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
// ORCA: realistic view - static shadows also light the scene from their fixed light.
|
||||
uniform bool use_static_light;
|
||||
uniform vec3 static_light_dir;
|
||||
vec3 top_light_dir() { return use_static_light ? static_light_dir : SHADOW_LIGHT_DIR; }
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying float color_clip_plane_dot;
|
||||
@@ -155,7 +159,7 @@ float shadow_shade()
|
||||
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
|
||||
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
|
||||
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
|
||||
float NdotL = dot(normalize(eye_normal), SHADOW_LIGHT_DIR);
|
||||
float NdotL = dot(normalize(eye_normal), top_light_dir());
|
||||
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
|
||||
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
|
||||
float sum = 0.0;
|
||||
|
||||
@@ -39,6 +39,10 @@ uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
// Color clip plane - general orientation. Used by the cut gizmo.
|
||||
uniform vec4 color_clip_plane;
|
||||
// ORCA: realistic view - static shadows also light the scene from their fixed light.
|
||||
uniform bool use_static_light;
|
||||
uniform vec3 static_light_dir;
|
||||
vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
@@ -60,11 +64,11 @@ void main()
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
float NdotL = max(dot(eye_normal, top_light_dir()), 0.0);
|
||||
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-top_light_dir(), eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
|
||||
@@ -70,6 +70,10 @@ uniform sampler2D shadow_map;
|
||||
uniform mat4 shadow_light_vp;
|
||||
uniform float shadow_intensity;
|
||||
uniform float shadow_map_texel;
|
||||
// ORCA: realistic view - static shadows also light the scene from their fixed light.
|
||||
uniform bool use_static_light;
|
||||
uniform vec3 static_light_dir;
|
||||
vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying float color_clip_plane_dot;
|
||||
@@ -193,7 +197,7 @@ float shadow_shade()
|
||||
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
|
||||
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
|
||||
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
|
||||
float NdotL = dot(normalize(eye_normal), LIGHT_TOP_DIR);
|
||||
float NdotL = dot(normalize(eye_normal), top_light_dir());
|
||||
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
|
||||
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
|
||||
float sum = 0.0;
|
||||
@@ -248,9 +252,9 @@ void main()
|
||||
vec3 normal = normalize(eye_normal);
|
||||
vec3 view_dir = normalize(-eye_position);
|
||||
|
||||
float NdotL_top = max(dot(normal, LIGHT_TOP_DIR), 0.0);
|
||||
float NdotL_top = max(dot(normal, top_light_dir()), 0.0);
|
||||
float diffuse = INTENSITY_AMBIENT + NdotL_top * LIGHT_TOP_DIFFUSE;
|
||||
vec3 half_top = normalize(LIGHT_TOP_DIR + view_dir);
|
||||
vec3 half_top = normalize(top_light_dir() + view_dir);
|
||||
float specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, half_top), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
float NdotL_front = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
|
||||
|
||||
@@ -6,6 +6,8 @@ uniform sampler2D shadow_map;
|
||||
uniform mat4 shadow_light_vp;
|
||||
uniform float shadow_intensity;
|
||||
uniform float shadow_map_texel;
|
||||
// Plate area a shadow can reach, min xy then max xy; the rest is skipped before any lookup.
|
||||
uniform vec4 shadow_bounds;
|
||||
|
||||
varying vec4 world_pos;
|
||||
|
||||
@@ -33,6 +35,8 @@ float shadow_occlusion()
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(world_pos.xy, shadow_bounds.xy)) || any(greaterThan(world_pos.xy, shadow_bounds.zw)))
|
||||
discard;
|
||||
float occ = shadow_occlusion();
|
||||
if (occ <= 0.0)
|
||||
discard;
|
||||
|
||||
@@ -72,6 +72,10 @@ uniform float shadow_map_texel;
|
||||
|
||||
// LIGHT_TOP_DIR in eye space (matches the diffuse light used for shading in gouraud.vs).
|
||||
const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
// ORCA: realistic view - static shadows also light the scene from their fixed light.
|
||||
uniform bool use_static_light;
|
||||
uniform vec3 static_light_dir;
|
||||
vec3 top_light_dir() { return use_static_light ? static_light_dir : SHADOW_LIGHT_DIR; }
|
||||
|
||||
in vec3 clipping_planes_dots;
|
||||
in float color_clip_plane_dot;
|
||||
@@ -206,7 +210,7 @@ float shadow_shade()
|
||||
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
|
||||
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
|
||||
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
|
||||
float NdotL = dot(normalize(eye_normal), SHADOW_LIGHT_DIR);
|
||||
float NdotL = dot(normalize(eye_normal), top_light_dir());
|
||||
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
|
||||
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
|
||||
float sum = 0.0;
|
||||
|
||||
@@ -39,6 +39,10 @@ uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
// Color clip plane - general orientation. Used by the cut gizmo.
|
||||
uniform vec4 color_clip_plane;
|
||||
// ORCA: realistic view - static shadows also light the scene from their fixed light.
|
||||
uniform bool use_static_light;
|
||||
uniform vec3 static_light_dir;
|
||||
vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
|
||||
|
||||
in vec3 v_position;
|
||||
in vec3 v_normal;
|
||||
@@ -60,11 +64,11 @@ void main()
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
float NdotL = max(dot(eye_normal, top_light_dir()), 0.0);
|
||||
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-top_light_dir(), eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
|
||||
@@ -80,6 +80,10 @@ uniform sampler2D shadow_map;
|
||||
uniform mat4 shadow_light_vp;
|
||||
uniform float shadow_intensity;
|
||||
uniform float shadow_map_texel;
|
||||
// ORCA: realistic view - static shadows also light the scene from their fixed light.
|
||||
uniform bool use_static_light;
|
||||
uniform vec3 static_light_dir;
|
||||
vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
|
||||
|
||||
in vec3 clipping_planes_dots;
|
||||
in float color_clip_plane_dot;
|
||||
@@ -250,7 +254,7 @@ float shadow_shade()
|
||||
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
|
||||
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
|
||||
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
|
||||
float NdotL = dot(normalize(eye_normal), LIGHT_TOP_DIR);
|
||||
float NdotL = dot(normalize(eye_normal), top_light_dir());
|
||||
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
|
||||
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
|
||||
float sum = 0.0;
|
||||
@@ -305,9 +309,9 @@ void main()
|
||||
vec3 normal = normalize(eye_normal);
|
||||
vec3 view_dir = normalize(-eye_position);
|
||||
|
||||
float NdotL_top = max(dot(normal, LIGHT_TOP_DIR), 0.0);
|
||||
float NdotL_top = max(dot(normal, top_light_dir()), 0.0);
|
||||
float diffuse = INTENSITY_AMBIENT + NdotL_top * LIGHT_TOP_DIFFUSE;
|
||||
vec3 half_top = normalize(LIGHT_TOP_DIR + view_dir);
|
||||
vec3 half_top = normalize(top_light_dir() + view_dir);
|
||||
float specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, half_top), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
float NdotL_front = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
|
||||
|
||||
@@ -6,6 +6,8 @@ uniform sampler2D shadow_map;
|
||||
uniform mat4 shadow_light_vp;
|
||||
uniform float shadow_intensity;
|
||||
uniform float shadow_map_texel;
|
||||
// Plate area a shadow can reach, min xy then max xy; the rest is skipped before any lookup.
|
||||
uniform vec4 shadow_bounds;
|
||||
|
||||
in vec4 world_pos;
|
||||
|
||||
@@ -35,6 +37,8 @@ float shadow_occlusion()
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(world_pos.xy, shadow_bounds.xy)) || any(greaterThan(world_pos.xy, shadow_bounds.zw)))
|
||||
discard;
|
||||
float occ = shadow_occlusion();
|
||||
if (occ <= 0.0)
|
||||
discard;
|
||||
|
||||
@@ -14,7 +14,7 @@ static std::atomic<std::uint32_t> g_dbg_id = 0;
|
||||
#endif
|
||||
|
||||
// Z for points from clip polygon
|
||||
static constexpr auto CLIP_IDX = std::numeric_limits<ClipperLib_Z::cInt>::max();
|
||||
static constexpr auto CLIP_IDX = std::numeric_limits<coord_t>::max();
|
||||
|
||||
static void cb_split_line(const ClipperZUtils::ZPoint& e1bot,
|
||||
const ClipperZUtils::ZPoint& e1top,
|
||||
@@ -91,14 +91,7 @@ SplittedLine do_split_line(const ClipperZUtils::ZPath& path, const ExPolygons& c
|
||||
clip_path.emplace_back(ClipperZUtils::to_zpath<false>(hole.points, CLIP_IDX));
|
||||
}
|
||||
|
||||
ClipperLib_Z::Clipper zclipper;
|
||||
zclipper.PreserveCollinear(true);
|
||||
zclipper.ZFillFunction(cb_split_line);
|
||||
zclipper.AddPaths(clip_path, ClipperLib_Z::ptClip, true);
|
||||
zclipper.AddPath(path, ClipperLib_Z::ptSubject, false);
|
||||
ClipperLib_Z::PolyTree polytree;
|
||||
zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), intersections);
|
||||
intersections = ClipperZUtils::clip_zpaths(ctIntersection, ClipperZUtils::ZPaths{ path }, true, clip_path, cb_split_line, true);
|
||||
}
|
||||
if (intersections.empty()) {
|
||||
return {};
|
||||
|
||||
@@ -50,7 +50,7 @@ SplittedLine split_line(const PathType& path, const ExPolygons& clip, bool close
|
||||
// Convert the input path into an open ZPath
|
||||
ClipperZUtils::ZPath p;
|
||||
p.reserve(path.size() + (closed ? 1 : 0));
|
||||
ClipperLib_Z::cInt z = 0;
|
||||
coord_t z = 0;
|
||||
for (const auto& point : path) {
|
||||
p.emplace_back(point.x(), point.y(), z);
|
||||
z++;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace Slic3r {
|
||||
namespace Algorithm {
|
||||
|
||||
// Calculating radius discretization according to ClipperLib offsetter code, see void ClipperOffset::DoOffset(double delta)
|
||||
// Calculating radius discretization according to the Clipper offsetter code, see ClipperOffset::DoGroupOffset()
|
||||
inline double clipper_round_offset_error(double offset, double arc_tolerance)
|
||||
{
|
||||
static constexpr const double def_arc_tolerance = 0.25;
|
||||
@@ -61,7 +61,6 @@ RegionExpansionParameters RegionExpansionParameters::build(
|
||||
|
||||
// Accuracy of the offsetter for wave propagation.
|
||||
out.arc_tolerance = scaled<double>(0.1);
|
||||
out.shortest_edge_length = out.initial_step * ClipperOffsetShortestEdgeFactor;
|
||||
|
||||
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
|
||||
// clipping during wave propagation. Needs to be in sync with the offsetter accuracy.
|
||||
@@ -75,25 +74,20 @@ RegionExpansionParameters RegionExpansionParameters::build(
|
||||
|
||||
// similar to expolygons_to_zpaths(), but each contour is expanded before converted to zpath.
|
||||
// The expanded contours are then opened (the first point is repeated at the end).
|
||||
static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened(
|
||||
static ClipperZUtils::ZPaths expolygons_to_zpaths_expanded_opened(
|
||||
const ExPolygons &src, const float expansion, coord_t &base_idx)
|
||||
{
|
||||
ClipperLib_Z::Paths out;
|
||||
ClipperZUtils::ZPaths out;
|
||||
out.reserve(2 * std::accumulate(src.begin(), src.end(), size_t(0),
|
||||
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
|
||||
ClipperLib::ClipperOffset offsetter;
|
||||
offsetter.ShortestEdgeLength = expansion * ClipperOffsetShortestEdgeFactor;
|
||||
ClipperLib::Paths expansion_cache;
|
||||
for (const ExPolygon &expoly : src) {
|
||||
for (size_t icontour = 0; icontour < expoly.num_contours(); ++ icontour) {
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
// contours will be CCW oriented even though the input paths are CW oriented.
|
||||
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
|
||||
offsetter.Clear();
|
||||
offsetter.AddPath(expoly.contour_or_hole(icontour).points, ClipperLib::jtSquare, ClipperLib::etClosedPolygon);
|
||||
expansion_cache.clear();
|
||||
offsetter.Execute(expansion_cache, icontour == 0 ? expansion : -expansion);
|
||||
append(out, ClipperZUtils::to_zpaths<true>(expansion_cache, base_idx));
|
||||
// Orient CCW, then grow the contour and shrink the holes. The output contours are CCW.
|
||||
Polygon contour = expoly.contour_or_hole(icontour);
|
||||
if (! contour.is_counter_clockwise())
|
||||
contour.reverse();
|
||||
for (const Polygon &expanded : offset(contour, icontour == 0 ? expansion : -expansion, jtSquare))
|
||||
out.emplace_back(ClipperZUtils::to_zpath<true>(expanded.points, base_idx));
|
||||
}
|
||||
++ base_idx;
|
||||
}
|
||||
@@ -104,21 +98,21 @@ static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened(
|
||||
// Thus some pieces of the clipped polygons may now become split at the ends of the source polygons.
|
||||
// Those ends are sorted lexicographically in "splits".
|
||||
// Reconnect those split pieces.
|
||||
static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector<std::pair<ClipperLib_Z::IntPoint, int>> &splits)
|
||||
static inline void merge_splits(ClipperZUtils::ZPaths &paths, std::vector<std::pair<ClipperZUtils::ZPoint, int>> &splits)
|
||||
{
|
||||
for (auto it_path = paths.begin(); it_path != paths.end(); ) {
|
||||
ClipperLib_Z::Path &path = *it_path;
|
||||
ClipperZUtils::ZPath &path = *it_path;
|
||||
assert(path.size() >= 2);
|
||||
bool merged = false;
|
||||
if (path.size() >= 2) {
|
||||
const ClipperLib_Z::IntPoint &front = path.front();
|
||||
const ClipperLib_Z::IntPoint &back = path.back();
|
||||
const ClipperZUtils::ZPoint &front = path.front();
|
||||
const ClipperZUtils::ZPoint &back = path.back();
|
||||
// The path before clipping was supposed to cross the clipping boundary or be fully out of it.
|
||||
// Thus the clipped contour is supposed to become open, with one exception: The anchor expands into a closed hole.
|
||||
if (front.x() != back.x() || front.y() != back.y()) {
|
||||
// Look up the ends in "splits", possibly join the contours.
|
||||
// "splits" maps into the other piece connected to the same end point.
|
||||
auto find_end = [&splits](const ClipperLib_Z::IntPoint &pt) -> std::pair<ClipperLib_Z::IntPoint, int>* {
|
||||
auto find_end = [&splits](const ClipperZUtils::ZPoint &pt) -> std::pair<ClipperZUtils::ZPoint, int>* {
|
||||
auto it = std::lower_bound(splits.begin(), splits.end(), pt,
|
||||
[](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r); });
|
||||
return it != splits.end() && it->first == pt ? &(*it) : nullptr;
|
||||
@@ -136,7 +130,7 @@ static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector<std::pai
|
||||
end->second = int(it_path - paths.begin());
|
||||
} else {
|
||||
// Open end was found and matched with end->second
|
||||
ClipperLib_Z::Path &other_path = paths[end->second];
|
||||
ClipperZUtils::ZPath &other_path = paths[end->second];
|
||||
polylines_merge(other_path, other_path.front() == end->first, std::move(path), end_front);
|
||||
if (std::next(it_path) == paths.end()) {
|
||||
paths.pop_back();
|
||||
@@ -212,36 +206,29 @@ std::vector<WaveSeed> wave_seeds(
|
||||
using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection;
|
||||
using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections;
|
||||
|
||||
ClipperLib_Z::Paths segments;
|
||||
Intersections intersections;
|
||||
ClipperZUtils::ZPaths segments;
|
||||
Intersections intersections;
|
||||
|
||||
coord_t idx_boundary_begin = 1;
|
||||
coord_t idx_boundary_end = idx_boundary_begin;
|
||||
coord_t idx_src_end;
|
||||
coord_t idx_boundary_begin = 1;
|
||||
coord_t idx_boundary_end = idx_boundary_begin;
|
||||
coord_t idx_src_end;
|
||||
|
||||
{
|
||||
ClipperLib_Z::Clipper zclipper;
|
||||
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
|
||||
zclipper.ZFillFunction(visitor.clipper_callback());
|
||||
// as closed contours
|
||||
zclipper.AddPaths(ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_end), ClipperLib_Z::ptClip, true);
|
||||
ClipperZUtils::ZPaths zboundary = ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_end);
|
||||
// as open contours
|
||||
std::vector<std::pair<ClipperLib_Z::IntPoint, int>> zsrc_splits;
|
||||
{
|
||||
idx_src_end = idx_boundary_end;
|
||||
ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_src_end);
|
||||
zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false);
|
||||
zsrc_splits.reserve(zsrc.size());
|
||||
for (const ClipperLib_Z::Path &path : zsrc) {
|
||||
assert(path.size() >= 2);
|
||||
assert(path.front() == path.back());
|
||||
zsrc_splits.emplace_back(path.front(), -1);
|
||||
}
|
||||
std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); });
|
||||
std::vector<std::pair<ClipperZUtils::ZPoint, int>> zsrc_splits;
|
||||
idx_src_end = idx_boundary_end;
|
||||
ClipperZUtils::ZPaths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_src_end);
|
||||
zsrc_splits.reserve(zsrc.size());
|
||||
for (const ClipperZUtils::ZPath &path : zsrc) {
|
||||
assert(path.size() >= 2);
|
||||
assert(path.front() == path.back());
|
||||
zsrc_splits.emplace_back(path.front(), -1);
|
||||
}
|
||||
ClipperLib_Z::PolyTree polytree;
|
||||
zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), segments);
|
||||
std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); });
|
||||
segments = ClipperZUtils::clip_zpaths(ctIntersection, zsrc, true, zboundary, visitor.clipper_callback());
|
||||
merge_splits(segments, zsrc_splits);
|
||||
}
|
||||
|
||||
@@ -256,10 +243,10 @@ std::vector<WaveSeed> wave_seeds(
|
||||
WaveSeeds out;
|
||||
out.reserve(segments.size());
|
||||
int iseed = 0;
|
||||
for (const ClipperLib_Z::Path &path : segments) {
|
||||
for (const ClipperZUtils::ZPath &path : segments) {
|
||||
assert(path.size() >= 2);
|
||||
ClipperLib_Z::IntPoint front = path.front();
|
||||
ClipperLib_Z::IntPoint back = path.back();
|
||||
ClipperZUtils::ZPoint front = path.front();
|
||||
ClipperZUtils::ZPoint back = path.back();
|
||||
// Both ends of a seed segment are supposed to be inside a single boundary expolygon.
|
||||
// Thus as long as the seed contour is not closed, it should be open at a boundary point.
|
||||
assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) ||
|
||||
@@ -280,7 +267,7 @@ std::vector<WaveSeed> wave_seeds(
|
||||
// boundary ID is not directly available).
|
||||
coord_t src_z = -1, boundary_z = -1;
|
||||
// Scan all path points for the information we need.
|
||||
for (const ClipperLib_Z::IntPoint &point : path) {
|
||||
for (const ClipperZUtils::ZPoint &point : path) {
|
||||
if (point.z() >= idx_boundary_end && point.z() < idx_src_end && src_z < 0)
|
||||
src_z = point.z();
|
||||
else if (point.z() >= idx_boundary_begin && point.z() < idx_boundary_end && boundary_z < 0)
|
||||
@@ -311,7 +298,7 @@ std::vector<WaveSeed> wave_seeds(
|
||||
// See https://github.com/prusa3d/PrusaSlicer/issues/12469.
|
||||
// Segement is open, yet its first point seems to be part of boundary polygon.
|
||||
// Take the first point with src polygon index.
|
||||
for (const ClipperLib_Z::IntPoint &point : path) {
|
||||
for (const ClipperZUtils::ZPoint &point : path) {
|
||||
if (point.z() >= idx_boundary_end) {
|
||||
front = point;
|
||||
back = point;
|
||||
@@ -360,17 +347,13 @@ std::vector<WaveSeed> wave_seeds(
|
||||
return out;
|
||||
}
|
||||
|
||||
static ClipperLib::Paths wavefront_initial(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polylines, float offset)
|
||||
static Polygons wavefront_initial(const VecOfPoints &polylines, float offset, double arc_tolerance)
|
||||
{
|
||||
ClipperLib::Paths out;
|
||||
Polygons out;
|
||||
out.reserve(polylines.size());
|
||||
ClipperLib::Paths out_this;
|
||||
for (const ClipperLib::Path &path : polylines) {
|
||||
for (const Points &path : polylines) {
|
||||
assert(path.size() >= 2);
|
||||
co.Clear();
|
||||
co.AddPath(path, jtRound, path.front() == path.back() ? ClipperLib::etClosedLine : ClipperLib::etOpenRound);
|
||||
co.Execute(out_this, offset);
|
||||
append(out, std::move(out_this));
|
||||
append(out, Slic3r::offset(Polyline(path), offset, jtRound, arc_tolerance, path.front() == path.back() ? etClosedLine : etOpenRound));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -378,43 +361,24 @@ static ClipperLib::Paths wavefront_initial(ClipperLib::ClipperOffset &co, const
|
||||
// Input polygons may consist of multiple expolygons, even nested expolygons.
|
||||
// After inflation some polygons may thus overlap, however the overlap is being resolved during the successive
|
||||
// clipping operation, thus it is not being done here.
|
||||
static ClipperLib::Paths wavefront_step(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polygons, float offset)
|
||||
static Polygons wavefront_step(const Polygons &polygons, float offset, double arc_tolerance)
|
||||
{
|
||||
ClipperLib::Paths out;
|
||||
Polygons out;
|
||||
out.reserve(polygons.size());
|
||||
ClipperLib::Paths out_this;
|
||||
for (const ClipperLib::Path &polygon : polygons) {
|
||||
co.Clear();
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
// contours will be CCW oriented even though the input paths are CW oriented.
|
||||
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
|
||||
co.AddPath(polygon, jtRound, ClipperLib::etClosedPolygon);
|
||||
bool ccw = ClipperLib::Orientation(polygon);
|
||||
co.Execute(out_this, ccw ? offset : - offset);
|
||||
if (! ccw) {
|
||||
// Reverse the resulting contours.
|
||||
for (ClipperLib::Path &path : out_this)
|
||||
std::reverse(path.begin(), path.end());
|
||||
}
|
||||
append(out, std::move(out_this));
|
||||
}
|
||||
for (const Polygon &polygon : polygons)
|
||||
// CCW contours grow, CW holes shrink.
|
||||
append(out, Slic3r::offset(polygon, offset, jtRound, arc_tolerance));
|
||||
return out;
|
||||
}
|
||||
|
||||
static ClipperLib::Paths wavefront_clip(const ClipperLib::Paths &wavefront, const Polygons &clipping)
|
||||
static Polygons wavefront_clip(const Polygons &wavefront, const Polygons &clipping)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(wavefront, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(ClipperUtils::PolygonsProvider(clipping), ClipperLib::ptClip, true);
|
||||
ClipperLib::Paths out;
|
||||
clipper.Execute(ClipperLib::ctIntersection, out, ClipperLib::pftPositive, ClipperLib::pftPositive);
|
||||
return out;
|
||||
return intersection(wavefront, clipping, pftPositive);
|
||||
}
|
||||
|
||||
static Polygons propagate_wave_from_boundary(
|
||||
ClipperLib::ClipperOffset &co,
|
||||
// Seed of the wave: Open polylines very close to the boundary.
|
||||
const ClipperLib::Paths &seed,
|
||||
const VecOfPoints &seed,
|
||||
// Boundary inside which the waveform will propagate.
|
||||
const ExPolygon &boundary,
|
||||
// How much to inflate the seed lines to produce the first wave area.
|
||||
@@ -425,25 +389,24 @@ static Polygons propagate_wave_from_boundary(
|
||||
const size_t num_other_steps,
|
||||
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
|
||||
// clipping during wave propagation.
|
||||
const float max_inflation)
|
||||
const float max_inflation,
|
||||
// Accuracy of the round offsets.
|
||||
const double arc_tolerance)
|
||||
{
|
||||
assert(! seed.empty() && seed.front().size() >= 2);
|
||||
Polygons clipping = ClipperUtils::clip_clipper_polygons_with_subject_bbox(boundary, get_extents<true>(seed).inflated(max_inflation));
|
||||
ClipperLib::Paths polygons = wavefront_clip(wavefront_initial(co, seed, initial_step), clipping);
|
||||
Polygons polygons = wavefront_clip(wavefront_initial(seed, initial_step, arc_tolerance), clipping);
|
||||
// Now offset the remaining
|
||||
for (size_t ioffset = 0; ioffset < num_other_steps; ++ ioffset)
|
||||
polygons = wavefront_clip(wavefront_step(co, polygons, other_step), clipping);
|
||||
return to_polygons(polygons);
|
||||
polygons = wavefront_clip(wavefront_step(polygons, other_step, arc_tolerance), clipping);
|
||||
return polygons;
|
||||
}
|
||||
|
||||
// Resulting regions are sorted by boundary id and source id.
|
||||
std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms)
|
||||
{
|
||||
std::vector<RegionExpansion> out;
|
||||
ClipperLib::Paths paths;
|
||||
ClipperLib::ClipperOffset co;
|
||||
co.ArcTolerance = params.arc_tolerance;
|
||||
co.ShortestEdgeLength = params.shortest_edge_length;
|
||||
VecOfPoints paths;
|
||||
for (auto it_seed = seeds.begin(); it_seed != seeds.end();) {
|
||||
auto it = it_seed;
|
||||
paths.clear();
|
||||
@@ -452,7 +415,7 @@ std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPol
|
||||
// Propagate the wavefront while clipping it with the trimmed boundary.
|
||||
// Collect the expanded polygons, merge them with the source polygons.
|
||||
RegionExpansion re;
|
||||
for (Polygon &polygon : propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], params.initial_step, params.other_step, params.num_other_steps, params.max_inflation))
|
||||
for (Polygon &polygon : propagate_wave_from_boundary(paths, boundary[it_seed->boundary], params.initial_step, params.other_step, params.num_other_steps, params.max_inflation, params.arc_tolerance))
|
||||
out.push_back({ std::move(polygon), it_seed->src, it_seed->boundary });
|
||||
it_seed = it;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ struct RegionExpansionParameters
|
||||
|
||||
// Accuracy of the offsetter for wave propagation.
|
||||
double arc_tolerance;
|
||||
double shortest_edge_length;
|
||||
|
||||
static RegionExpansionParameters build(
|
||||
// Scaled expansion value
|
||||
|
||||
@@ -304,6 +304,9 @@ void AppConfig::set_defaults()
|
||||
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
|
||||
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
|
||||
|
||||
if (get(SETTING_OPENGL_SHOW_RENDER_TIMINGS).empty())
|
||||
set_bool(SETTING_OPENGL_SHOW_RENDER_TIMINGS, false);
|
||||
|
||||
if (get(SETTING_OPENGL_REALISTIC_MODE).empty())
|
||||
set_bool(SETTING_OPENGL_REALISTIC_MODE, false);
|
||||
|
||||
@@ -316,8 +319,9 @@ void AppConfig::set_defaults()
|
||||
if (get(SETTING_OPENGL_SHADING_MODEL).empty())
|
||||
set(SETTING_OPENGL_SHADING_MODEL, "gouraud");
|
||||
|
||||
if (get(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS).empty())
|
||||
set_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS, false);
|
||||
// Replaces the on/off setting, whose shadows turned with the camera.
|
||||
if (get(SETTING_OPENGL_REALISTIC_SHADOWS).empty())
|
||||
set(SETTING_OPENGL_REALISTIC_SHADOWS, get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS) ? "orbit" : "off");
|
||||
|
||||
if (get(SETTING_OPENGL_PHONG_SMOOTH_NORMALS).empty())
|
||||
set_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS, false);
|
||||
|
||||
@@ -37,10 +37,13 @@ using namespace nlohmann;
|
||||
#define SETTING_OPENGL_SCENE_CACHE "opengl_scene_cache"
|
||||
#define SETTING_OPENGL_SKIP_IDENTICAL_FRAMES "opengl_skip_identical_frames"
|
||||
#define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay"
|
||||
#define SETTING_OPENGL_SHOW_RENDER_TIMINGS "opengl_show_render_timings"
|
||||
#define SETTING_OPENGL_REALISTIC_MODE "opengl_realistic_mode"
|
||||
#define SETTING_OPENGL_REALISTIC_PHONG "opengl_realistic_phong"
|
||||
#define SETTING_OPENGL_SHADING_MODEL "opengl_shading_model"
|
||||
#define SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS "opengl_phong_basic_plate_shadows"
|
||||
// off, static (light fixed in the world) or orbit (light turning with the camera)
|
||||
#define SETTING_OPENGL_REALISTIC_SHADOWS "opengl_realistic_shadows"
|
||||
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
|
||||
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
|
||||
#define SETTING_OPENGL_REALISTIC_PREVIEW "opengl_realistic_preview"
|
||||
|
||||
@@ -263,10 +263,8 @@ std::unique_ptr<LocToLineGrid> cre
|
||||
*/
|
||||
void fixSelfIntersections(const coord_t epsilon, Polygons &thiss)
|
||||
{
|
||||
if (epsilon < 1) {
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd);
|
||||
if (epsilon < 1)
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t half_epsilon = (epsilon + 1) / 2;
|
||||
|
||||
@@ -304,8 +302,6 @@ void fixSelfIntersections(const coord_t epsilon, Polygons &thiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -352,18 +348,11 @@ void removeDegenerateVerts(Polygons &thiss)
|
||||
|
||||
void removeSmallAreas(Polygons &thiss, const double min_area_size, const bool remove_holes)
|
||||
{
|
||||
auto to_path = [](const Polygon &poly) -> ClipperLib::Path {
|
||||
ClipperLib::Path out;
|
||||
for (const Point &pt : poly.points)
|
||||
out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y()));
|
||||
return out;
|
||||
};
|
||||
|
||||
auto new_end = thiss.end();
|
||||
if (remove_holes) {
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
// All polygons smaller than target are removed by replacing them with a polygon from the back of the vector.
|
||||
if (fabs(ClipperLib::Area(to_path(*it))) < min_area_size) {
|
||||
if (fabs(it->area()) < min_area_size) {
|
||||
--new_end;
|
||||
*it = std::move(*new_end);
|
||||
continue; // Don't increment the iterator such that the polygon just swapped in is checked next.
|
||||
@@ -374,7 +363,7 @@ void removeSmallAreas(Polygons &thiss, const double min_area_size, const bool re
|
||||
// For each polygon, computes the signed area, move small outlines at the end of the vector and keep pointer on small holes
|
||||
Polygons small_holes;
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
if (double area = ClipperLib::Area(to_path(*it)); fabs(area) < min_area_size) {
|
||||
if (double area = it->area(); fabs(area) < min_area_size) {
|
||||
if (area >= 0) {
|
||||
--new_end;
|
||||
if (it < new_end) {
|
||||
@@ -773,7 +762,7 @@ void WallToolPaths::separateOutInnerContour()
|
||||
//To get a correct shape, we need to make the outside contour positive and any holes inside negative.
|
||||
//This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon.
|
||||
//The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation.
|
||||
inner_contour = union_(inner_contour, ClipperLib::PolyFillType::pftEvenOdd);
|
||||
inner_contour = union_(inner_contour, pftEvenOdd);
|
||||
}
|
||||
|
||||
const Polygons& WallToolPaths::getInnerContour()
|
||||
|
||||
@@ -287,9 +287,9 @@ double ExtrusionLine::area() const
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace Slic3r {
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow)
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperZUtils::ZPaths &extrusion_paths, const ExtrusionRole role, const Flow &flow)
|
||||
{
|
||||
for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) {
|
||||
for (const ClipperZUtils::ZPath &extrusion_path : extrusion_paths) {
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path);
|
||||
Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled<float>(0.05), float(SCALED_EPSILON)).paths);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#ifndef UTILS_EXTRUSION_LINE_H
|
||||
#define UTILS_EXTRUSION_LINE_H
|
||||
|
||||
#include <clipper/clipper_z.hpp>
|
||||
#include "../../ClipperZUtils.hpp"
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -294,7 +294,7 @@ using VariableWidthLines = std::vector<ExtrusionLine>; //<! The ExtrusionLines g
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow);
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperZUtils::ZPaths &extrusion_paths, const ExtrusionRole role, const Flow &flow);
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Slic3r {
|
||||
|
||||
template<class Tout = double, class = FloatingOnly<Tout>, int...EigenArgs>
|
||||
inline constexpr Eigen::Matrix<Tout, 2, EigenArgs...> unscaled(
|
||||
const Slic3r::ClipperLib::IntPoint &v) noexcept
|
||||
const Slic3r::Point &v) noexcept
|
||||
{
|
||||
return Eigen::Matrix<Tout, 2, EigenArgs...>{unscaled<Tout>(v.x()),
|
||||
unscaled<Tout>(v.y())};
|
||||
@@ -405,7 +405,7 @@ protected:
|
||||
// 2) X distance of item corner to bed corner (low weight)
|
||||
// 3) item row occupancy (useful when rotation is enabled)
|
||||
// 4)需要允许往屏蔽区域的左边或下边去一点,不然很多物体可能认为摆不进去,实际上我们最后是可以做平移的
|
||||
double dist_for_BOTTOM_LEFT(Box ibb, const ClipperLib::IntPoint& origin_pack)
|
||||
double dist_for_BOTTOM_LEFT(Box ibb, const Slic3r::Point& origin_pack)
|
||||
{
|
||||
double dist_corner_y = ibb.minCorner().y() - origin_pack.y();
|
||||
double dist_corner_x = ibb.minCorner().x() - origin_pack.x();
|
||||
@@ -421,7 +421,7 @@ protected:
|
||||
return bindist;
|
||||
}
|
||||
|
||||
double dist_to_bin(const Box& ibb, const ClipperLib::IntPoint& origin_pack, typename Packer::PlacementConfig::Alignment starting_point_alignment)
|
||||
double dist_to_bin(const Box& ibb, const Slic3r::Point& origin_pack, typename Packer::PlacementConfig::Alignment starting_point_alignment)
|
||||
{
|
||||
double bindist = 0;
|
||||
if (starting_point_alignment == PConfig::Alignment::BOTTOM_LEFT)
|
||||
@@ -439,7 +439,7 @@ protected:
|
||||
// as it possibly can be but at the same time, it has to provide
|
||||
// reasonable results.
|
||||
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
||||
objfunc(const Item &item, const ClipperLib::IntPoint &origin_pack)
|
||||
objfunc(const Item &item, const Slic3r::Point &origin_pack)
|
||||
{
|
||||
const double bin_area = m_bin_area;
|
||||
const SpatIndex& spatindex = m_rtree;
|
||||
@@ -1014,7 +1014,12 @@ void _arrange(
|
||||
inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};}
|
||||
inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); }
|
||||
inline ExPolygon to_nestbin(const Polygon &p) { return ExPolygon{p}; }
|
||||
inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); }
|
||||
// libnest2d's infinite box reaches the int64 limit, where Clipper2's double math is no longer exact.
|
||||
inline Box to_nestbin(const InfiniteBed &bed)
|
||||
{
|
||||
const coord_t r = coord_t(1) << 50;
|
||||
return Box{{bed.center.x() - r, bed.center.y() - r}, {bed.center.x() + r, bed.center.y() + r}};
|
||||
}
|
||||
|
||||
inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); }
|
||||
inline coord_t height(const BoundingBox& box) { return box.max.y() - box.min.y(); }
|
||||
@@ -1122,8 +1127,6 @@ void arrange(ArrangePolygons & arrangables,
|
||||
const BedT & bed,
|
||||
const ArrangeParams & params)
|
||||
{
|
||||
namespace clppr = Slic3r::ClipperLib;
|
||||
|
||||
std::vector<Item> items, fixeditems;
|
||||
items.reserve(arrangables.size());
|
||||
|
||||
|
||||
@@ -94,12 +94,9 @@ set(lisbslic3r_sources
|
||||
calib.hpp
|
||||
Circle.cpp
|
||||
Circle.hpp
|
||||
clipper.cpp
|
||||
clipper.hpp
|
||||
ClipperUtils.cpp
|
||||
ClipperUtils.hpp
|
||||
Clipper2Utils.cpp
|
||||
Clipper2Utils.hpp
|
||||
ClipperZUtils.cpp
|
||||
ClipperZUtils.hpp
|
||||
Color.cpp
|
||||
Color.hpp
|
||||
@@ -716,7 +713,6 @@ target_link_libraries(libslic3r
|
||||
${OCCT_LIBS}
|
||||
boost_libs
|
||||
cereal::cereal
|
||||
clipper
|
||||
Clipper2
|
||||
draco::draco
|
||||
glu-libtess
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
#include "Clipper2Utils.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "clipper2/clipper.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//BBS: FIXME
|
||||
Slic3r::Polylines Paths64_to_polylines(const Clipper2Lib::Paths64& in)
|
||||
{
|
||||
Slic3r::Polylines out;
|
||||
out.reserve(in.size());
|
||||
for (const Clipper2Lib::Path64& path64 : in) {
|
||||
Slic3r::Points points;
|
||||
points.reserve(path64.size());
|
||||
for (const Clipper2Lib::Point64& point64 : path64)
|
||||
points.emplace_back(Slic3r::Point(point64.x, point64.y));
|
||||
out.emplace_back(Slic3r::Polyline(points));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
//BBS: FIXME
|
||||
template <typename Container>
|
||||
Clipper2Lib::Paths64 Slic3rPoints_to_Paths64(const Container& in)
|
||||
{
|
||||
Clipper2Lib::Paths64 out;
|
||||
out.reserve(in.size());
|
||||
for (const auto& item : in) {
|
||||
Clipper2Lib::Path64 path;
|
||||
path.reserve(item.size());
|
||||
for (const Slic3r::Point& point : item.points)
|
||||
path.emplace_back(Clipper2Lib::Point64(point.x(), point.y()));
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Clipper2Lib::Paths64 Slic3rPolylines_to_Paths64(const Polylines& in)
|
||||
{
|
||||
return Slic3rPoints_to_Paths64(in);
|
||||
}
|
||||
|
||||
Points Path64ToPoints(const Clipper2Lib::Path64& path64)
|
||||
{
|
||||
Points points;
|
||||
points.reserve(path64.size());
|
||||
for (const Clipper2Lib::Point64 &point64 : path64) points.emplace_back(Slic3r::Point(point64.x, point64.y));
|
||||
return points;
|
||||
}
|
||||
|
||||
static ExPolygons PolyTreeToExPolygons(Clipper2Lib::PolyTree64 &&polytree)
|
||||
{
|
||||
struct Inner
|
||||
{
|
||||
static void PolyTreeToExPolygonsRecursive(Clipper2Lib::PolyTree64 &&polynode, ExPolygons *expolygons)
|
||||
{
|
||||
size_t cnt = expolygons->size();
|
||||
expolygons->resize(cnt + 1);
|
||||
(*expolygons)[cnt].contour.points = Path64ToPoints(polynode.Polygon());
|
||||
|
||||
(*expolygons)[cnt].holes.resize(polynode.Count());
|
||||
for (int i = 0; i < polynode.Count(); ++i) {
|
||||
(*expolygons)[cnt].holes[i].points = Path64ToPoints(polynode[i]->Polygon());
|
||||
// Add outer polygons contained by (nested within) holes.
|
||||
for (int j = 0; j < polynode[i]->Count(); ++j) PolyTreeToExPolygonsRecursive(std::move(*polynode[i]->Child(j)), expolygons);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t PolyTreeCountExPolygons(const Clipper2Lib::PolyPath64& polynode)
|
||||
{
|
||||
size_t cnt = 1;
|
||||
for (size_t i = 0; i < polynode.Count(); ++i) {
|
||||
for (size_t j = 0; j < polynode.Child(i)->Count(); ++j) cnt += PolyTreeCountExPolygons(*polynode.Child(i)->Child(j));
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
};
|
||||
|
||||
ExPolygons retval;
|
||||
size_t cnt = 0;
|
||||
for (int i = 0; i < polytree.Count(); ++i) cnt += Inner::PolyTreeCountExPolygons(*polytree[i]);
|
||||
retval.reserve(cnt);
|
||||
for (int i = 0; i < polytree.Count(); ++i) Inner::PolyTreeToExPolygonsRecursive(std::move(*polytree[i]), &retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
void SimplifyPolyTree(const Clipper2Lib::PolyPath64 &polytree, double epsilon, Clipper2Lib::PolyPath64 &result)
|
||||
{
|
||||
for (const auto &child : polytree) {
|
||||
Clipper2Lib::PolyPath64 *newchild = result.AddChild(Clipper2Lib::SimplifyPath(child->Polygon(), epsilon));
|
||||
SimplifyPolyTree(*child, epsilon, *newchild);
|
||||
}
|
||||
}
|
||||
|
||||
Clipper2Lib::Paths64 Slic3rPolygons_to_Paths64(const Polygons &in)
|
||||
{
|
||||
Clipper2Lib::Paths64 out;
|
||||
out.reserve(in.size());
|
||||
for (const Polygon &poly : in) {
|
||||
Clipper2Lib::Path64 path;
|
||||
path.reserve(poly.points.size());
|
||||
for (const Slic3r::Point &point : poly.points) path.emplace_back(Clipper2Lib::Point64(point.x(), point.y()));
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Clipper2Lib::Paths64 Slic3rExPolygons_to_Paths64(const ExPolygons& in)
|
||||
{
|
||||
Clipper2Lib::Paths64 out;
|
||||
out.reserve(in.size());
|
||||
for (const ExPolygon& expolygon : in) {
|
||||
for (size_t i = 0; i < expolygon.num_contours(); i++) {
|
||||
const auto &poly = expolygon.contour_or_hole(i);
|
||||
Clipper2Lib::Path64 path;
|
||||
path.reserve(poly.points.size());
|
||||
for (const Slic3r::Point &point : poly.points) path.emplace_back(Clipper2Lib::Point64(point.x(), point.y()));
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Polylines _clipper2_pl_open(Clipper2Lib::ClipType clipType, const Slic3r::Polylines& subject, const Slic3r::Polygons& clip)
|
||||
{
|
||||
Clipper2Lib::Clipper64 c;
|
||||
c.AddOpenSubject(Slic3rPoints_to_Paths64(subject));
|
||||
c.AddClip(Slic3rPoints_to_Paths64(clip));
|
||||
|
||||
Clipper2Lib::ClipType ct = clipType;
|
||||
Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero;
|
||||
Clipper2Lib::Paths64 solution, solution_open;
|
||||
c.Execute(ct, fr, solution, solution_open);
|
||||
|
||||
Slic3r::Polylines out;
|
||||
out.reserve(solution.size() + solution_open.size());
|
||||
polylines_append(out, Paths64_to_polylines(solution));
|
||||
polylines_append(out, Paths64_to_polylines(solution_open));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip)
|
||||
{ return _clipper2_pl_open(Clipper2Lib::ClipType::Intersection, subject, clip); }
|
||||
Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip)
|
||||
{ return _clipper2_pl_open(Clipper2Lib::ClipType::Difference, subject, clip); }
|
||||
|
||||
ExPolygons union_ex_2(const Polygons& polygons)
|
||||
{
|
||||
Clipper2Lib::Clipper64 c;
|
||||
c.AddSubject(Slic3rPolygons_to_Paths64(polygons));
|
||||
|
||||
Clipper2Lib::ClipType ct = Clipper2Lib::ClipType::Union;
|
||||
Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero;
|
||||
Clipper2Lib::PolyTree64 solution;
|
||||
c.Execute(ct, fr, solution);
|
||||
|
||||
ExPolygons results = PolyTreeToExPolygons(std::move(solution));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
ExPolygons union_ex_2(const ExPolygons &expolygons)
|
||||
{
|
||||
Clipper2Lib::Clipper64 c;
|
||||
c.AddSubject(Slic3rExPolygons_to_Paths64(expolygons));
|
||||
|
||||
Clipper2Lib::ClipType ct = Clipper2Lib::ClipType::Union;
|
||||
Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero;
|
||||
Clipper2Lib::PolyTree64 solution;
|
||||
c.Execute(ct, fr, solution);
|
||||
|
||||
ExPolygons results = PolyTreeToExPolygons(std::move(solution));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 对 ExPolygons 进行偏移
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta)
|
||||
{
|
||||
Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons);
|
||||
Clipper2Lib::ClipperOffset offsetter;
|
||||
offsetter.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon);
|
||||
Clipper2Lib::PolyPath64 polytree;
|
||||
offsetter.Execute(delta, polytree);
|
||||
ExPolygons results = PolyTreeToExPolygons(std::move(polytree));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta, Clipper2Lib::JoinType joinType)
|
||||
{
|
||||
Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons);
|
||||
Clipper2Lib::ClipperOffset offsetter;
|
||||
offsetter.AddPaths(subject, joinType, Clipper2Lib::EndType::Polygon);
|
||||
Clipper2Lib::PolyPath64 polytree;
|
||||
offsetter.Execute(delta, polytree);
|
||||
ExPolygons results = PolyTreeToExPolygons(std::move(polytree));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
ExPolygons offset2_ex_2(const ExPolygons& expolygons, double delta1, double delta2)
|
||||
{
|
||||
// 1st offset
|
||||
Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons);
|
||||
Clipper2Lib::ClipperOffset offsetter;
|
||||
offsetter.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon);
|
||||
Clipper2Lib::PolyPath64 polytree;
|
||||
offsetter.Execute(delta1, polytree);
|
||||
|
||||
// simplify the result
|
||||
Clipper2Lib::PolyPath64 polytree2;
|
||||
SimplifyPolyTree(polytree, SCALED_EPSILON, polytree2);
|
||||
|
||||
// 2nd offset
|
||||
offsetter.Clear();
|
||||
offsetter.AddPaths(Clipper2Lib::PolyTreeToPaths64(polytree2), Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon);
|
||||
polytree.Clear();
|
||||
offsetter.Execute(delta2, polytree);
|
||||
|
||||
// convert back to expolygons
|
||||
ExPolygons results = PolyTreeToExPolygons(std::move(polytree));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef slic3r_Clipper2Utils_hpp_
|
||||
#define slic3r_Clipper2Utils_hpp_
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include "clipper2/clipper.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
Clipper2Lib::Paths64 Slic3rPolylines_to_Paths64(const Slic3r::Polylines& in);
|
||||
Slic3r::Polylines Paths64_to_polylines(const Clipper2Lib::Paths64& in);
|
||||
Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip);
|
||||
Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip);
|
||||
ExPolygons union_ex_2(const Polygons &expolygons);
|
||||
ExPolygons union_ex_2(const ExPolygons &expolygons);
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta);
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta, Clipper2Lib::JoinType joinType);
|
||||
ExPolygons offset2_ex_2(const ExPolygons &expolygons, double delta1, double delta2);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,167 +0,0 @@
|
||||
#ifndef slic3r_Clipper2ZUtils_hpp_
|
||||
#define slic3r_Clipper2ZUtils_hpp_
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include <clipper2/clipper2_z.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
namespace Slic3r { namespace Clipper2ZUtils {
|
||||
|
||||
using ZPoint64 = Clipper2Lib_Z::Point64;
|
||||
using ZPoints64 = Clipper2Lib_Z::Path64;
|
||||
using ZPath64 = Clipper2Lib_Z::Path64;
|
||||
using ZPaths64 = Clipper2Lib_Z::Paths64;
|
||||
|
||||
inline bool zpoint64_lower(const ZPoint64 &l, const ZPoint64 &r) {
|
||||
return l.x < r.x || (l.x == r.x && (l.y < r.y || (l.y == r.y && l.z < r.z)));
|
||||
}
|
||||
|
||||
// Convert a single path to zpath with a given Z coordinate.
|
||||
// If Open, then duplicate the first point at the end.
|
||||
template<bool Open = false>
|
||||
inline ZPath64 to_zpath64(const Points &path, int64_t z)
|
||||
{
|
||||
ZPath64 out;
|
||||
if (!path.empty()) {
|
||||
out.reserve(path.size() + (Open ? 1 : 0));
|
||||
for (const Point &p : path) out.emplace_back(p.x(), p.y(), z);
|
||||
if (Open) out.emplace_back(out.front());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template<bool Open = false>
|
||||
inline ZPath64 to_zpath64(const Clipper2Lib_Z::Path64 &path, int64_t z)
|
||||
{
|
||||
ZPath64 out;
|
||||
if (!path.empty()) {
|
||||
out.reserve(path.size() + (Open ? 1 : 0));
|
||||
for (const Clipper2Lib_Z::Point64 &p : path) out.emplace_back(p.x, p.y, z);
|
||||
if (Open) out.emplace_back(out.front());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple paths to zpaths with a given Z coordinate.
|
||||
template<bool Open = false>
|
||||
inline ZPaths64 to_zpaths64(const VecOfPoints &paths, int64_t z)
|
||||
{
|
||||
ZPaths64 out;
|
||||
out.reserve(paths.size());
|
||||
for (const Points &path : paths) out.emplace_back(to_zpath64<Open>(path, z));
|
||||
return out;
|
||||
}
|
||||
|
||||
template<bool Open = false>
|
||||
inline ZPaths64 to_zpaths64(const Clipper2Lib_Z::Paths64 &paths, int64_t z)
|
||||
{
|
||||
ZPaths64 out;
|
||||
out.reserve(paths.size());
|
||||
for (const Clipper2Lib_Z::Path64 &path : paths) out.emplace_back(to_zpath64<Open>(path, z));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple expolygons into zpaths with Z specified by index
|
||||
// offset by base_idx.
|
||||
template<bool Open = false>
|
||||
inline ZPaths64 expolygons_to_zpaths64(const ExPolygons &src, int64_t &base_idx)
|
||||
{
|
||||
ZPaths64 out;
|
||||
out.reserve(std::accumulate(src.begin(), src.end(), size_t(0),
|
||||
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
|
||||
for (const ExPolygon &expoly : src) {
|
||||
out.emplace_back(to_zpath64<Open>(expoly.contour.points, base_idx));
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
out.emplace_back(to_zpath64<Open>(hole.points, base_idx));
|
||||
++base_idx;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple expolygons into zpaths with the same Z.
|
||||
template<bool Open = false>
|
||||
inline ZPaths64 expolygons_to_zpaths64_with_same_z(const ExPolygons &src, int64_t z)
|
||||
{
|
||||
ZPaths64 out;
|
||||
out.reserve(std::accumulate(src.begin(), src.end(), size_t(0),
|
||||
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
|
||||
for (const ExPolygon &expoly : src) {
|
||||
out.emplace_back(to_zpath64<Open>(expoly.contour.points, z));
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
out.emplace_back(to_zpath64<Open>(hole.points, z));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert a zpath back to 2D Points.
|
||||
// If Open, then duplicate the first point at the end.
|
||||
template<bool Open = false>
|
||||
inline Points from_zpath64(const ZPath64 &path)
|
||||
{
|
||||
Points out;
|
||||
if (!path.empty()) {
|
||||
out.reserve(path.size() + (Open ? 1 : 0));
|
||||
for (const ZPoint64 &p : path) out.emplace_back(p.x, p.y);
|
||||
if (Open) out.emplace_back(out.front());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple zpaths back to 2D paths.
|
||||
template<bool Open = false>
|
||||
inline void from_zpaths64(const ZPaths64 &paths, VecOfPoints &out)
|
||||
{
|
||||
out.reserve(out.size() + paths.size());
|
||||
for (const ZPath64 &path : paths) out.emplace_back(from_zpath64<Open>(path));
|
||||
}
|
||||
template<bool Open = false>
|
||||
inline VecOfPoints from_zpaths64(const ZPaths64 &paths)
|
||||
{
|
||||
VecOfPoints out;
|
||||
from_zpaths64<Open>(paths, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Intersection visitor for Clipper2 (zCallback_).
|
||||
class Clipper2ZIntersectionVisitor
|
||||
{
|
||||
public:
|
||||
using Intersection = std::pair<int64_t, int64_t>;
|
||||
using Intersections = std::vector<Intersection>;
|
||||
|
||||
Clipper2ZIntersectionVisitor(Intersections &intersections) : m_intersections(intersections) {}
|
||||
|
||||
void reset() { m_intersections.clear(); }
|
||||
|
||||
void operator()(const ZPoint64 &e1bot, const ZPoint64 &e1top, const ZPoint64 &e2bot, const ZPoint64 &e2top, ZPoint64 &pt)
|
||||
{
|
||||
std::array<int64_t, 4> srcs{e1bot.z, e1top.z, e2bot.z, e2top.z};
|
||||
std::sort(srcs.begin(), srcs.end());
|
||||
auto it = std::unique(srcs.begin(), srcs.end());
|
||||
int new_size = std::distance(srcs.begin(), it);
|
||||
assert(new_size == 1 || new_size == 2);
|
||||
if (new_size == 1) {
|
||||
pt.z = srcs[0];
|
||||
}
|
||||
else if(new_size == 2){
|
||||
m_intersections.emplace_back(srcs[0], srcs[1]);
|
||||
pt.z = -int64_t(m_intersections.size());
|
||||
}
|
||||
}
|
||||
|
||||
auto clipper_callback()
|
||||
{
|
||||
return [this](const ZPoint64 &e1bot, const ZPoint64 &e1top,
|
||||
const ZPoint64 &e2bot, const ZPoint64 &e2top, ZPoint64 &pt) {
|
||||
return (*this)(e1bot, e1top, e2bot, e2top, pt); };
|
||||
}
|
||||
|
||||
const Intersections &intersections() const { return m_intersections; }
|
||||
|
||||
private:
|
||||
Intersections &m_intersections;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::Clipper2ZUtils
|
||||
#endif // slic3r_Clipper2ZUtils_hpp_
|
||||
@@ -2,23 +2,32 @@
|
||||
#define slic3r_ClipperUtils_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "clipper.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Surface.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Offset joins and ends, fill rules and boolean operations, named as in Clipper1.
|
||||
enum JoinType { jtSquare, jtRound, jtMiter };
|
||||
enum EndType { etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound };
|
||||
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
|
||||
enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
// import these wherever we're included
|
||||
using Slic3r::ClipperLib::jtMiter;
|
||||
using Slic3r::ClipperLib::jtRound;
|
||||
using Slic3r::ClipperLib::jtSquare;
|
||||
using Slic3r::jtMiter;
|
||||
using Slic3r::jtRound;
|
||||
using Slic3r::jtSquare;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static constexpr const float ClipperSafetyOffset = 10.f;
|
||||
|
||||
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter;
|
||||
static constexpr const JoinType DefaultJoinType = jtMiter;
|
||||
|
||||
static constexpr const Slic3r::ClipperLib::EndType DefaultEndType = Slic3r::ClipperLib::etOpenButt;
|
||||
static constexpr const EndType DefaultEndType = etOpenButt;
|
||||
|
||||
//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2.
|
||||
// Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill.
|
||||
@@ -26,7 +35,7 @@ static constexpr const Slic3r::ClipperLib::EndType DefaultEndType = Sl
|
||||
// is extended excessively.
|
||||
static constexpr const double DefaultMiterLimit = 3.;
|
||||
|
||||
static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Slic3r::ClipperLib::jtSquare;
|
||||
static constexpr const JoinType DefaultLineJoinType = jtSquare;
|
||||
// Miter limit is ignored for jtSquare.
|
||||
static constexpr const double DefaultLineMiterLimit = 0.;
|
||||
|
||||
@@ -304,12 +313,12 @@ namespace ClipperUtils {
|
||||
};
|
||||
|
||||
|
||||
// For ClipperLib with Z coordinates.
|
||||
// Points with a Z coordinate.
|
||||
using ZPoint = Vec3i32;
|
||||
using ZPoints = std::vector<Vec3i32>;
|
||||
|
||||
// Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon.
|
||||
// Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one
|
||||
// Useful as an optimization for expensive Clipper operations, for example when clipping source polygons one by one
|
||||
// with a set of polygons covering the whole layer below.
|
||||
void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out, const bool get_entire_polygons = false);
|
||||
void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out);
|
||||
@@ -323,31 +332,28 @@ namespace ClipperUtils {
|
||||
|
||||
}
|
||||
|
||||
// Perform union of input polygons using the non-zero rule, convert to ExPolygons.
|
||||
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false);
|
||||
|
||||
// offset Polygons
|
||||
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
|
||||
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
|
||||
// offset Polylines
|
||||
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
|
||||
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
|
||||
Slic3r::Polygons offset(const Slic3r::Polyline3 &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
|
||||
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
|
||||
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, EndType end_type = DefaultEndType);
|
||||
Slic3r::Polygons offset(const Slic3r::Polyline3 &polyline, const float delta, JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, EndType end_type = DefaultEndType);
|
||||
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, EndType end_type = DefaultEndType);
|
||||
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
// BBS
|
||||
inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{
|
||||
Slic3r::Polygons temp;
|
||||
temp.push_back(polygon);
|
||||
@@ -356,8 +362,8 @@ inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float
|
||||
}
|
||||
|
||||
// convert stroke to path by offsetting of contour
|
||||
Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
|
||||
Polygons contour_to_polygons(const Polygons &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
|
||||
Polygons contour_to_polygons(const Polygon &polygon, const float line_width, JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
|
||||
Polygons contour_to_polygons(const Polygons &polygon, const float line_width, JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
|
||||
|
||||
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
|
||||
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
|
||||
@@ -370,62 +376,62 @@ Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons);
|
||||
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons);
|
||||
|
||||
// Aliases for the various offset(...) functions, conveying the purpose of the offset.
|
||||
inline Slic3r::Polygons expand(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::Polygons expand(const Slic3r::Polygon &polygon, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset(polygon, delta, joinType, miterLimit); }
|
||||
inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); }
|
||||
inline Slic3r::Polygons expand(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::Polygons expand(const Slic3r::ExPolygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, delta, joinType, miterLimit); }
|
||||
// Input polygons for shrinking shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
|
||||
|
||||
// Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
|
||||
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
|
||||
// BBS
|
||||
Slic3r::ExPolygons _clipper_ex(ClipperLib::ClipType clipType,
|
||||
Slic3r::ExPolygons _clipper_ex(ClipType clipType,
|
||||
const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
|
||||
|
||||
|
||||
// Offset outside, then inside produces morphological closing. All deltas should be positive.
|
||||
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return closing(polygons, delta, delta, joinType, miterLimit); }
|
||||
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return closing_ex(polygons, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(polygons, delta, - delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(surfaces, delta, - delta, joinType, miterLimit); }
|
||||
|
||||
// Offset inside, then outside produces morphological opening. All deltas should be positive.
|
||||
// Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return opening(polygons, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return opening(expolygons, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return opening(surfaces, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(polygons, - delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(surfaces, - delta, delta, joinType, miterLimit); }
|
||||
|
||||
Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Lines _clipper_ln(ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip);
|
||||
|
||||
// Safety offset is applied to the clipping polygons only.
|
||||
Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
@@ -489,13 +495,14 @@ inline Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons& subject, const Slic3
|
||||
|
||||
inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip)
|
||||
{
|
||||
return _clipper_ln(ClipperLib::ctDifference, subject, clip);
|
||||
return _clipper_ln(ctDifference, subject, clip);
|
||||
}
|
||||
|
||||
// Safety offset is applied to the clipping polygons only.
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, PolyFillType fill_type);
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox().
|
||||
// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon.
|
||||
@@ -534,135 +541,42 @@ void restore_source_path_order(const Slic3r::Polyline &source, Slic3r::Polylines
|
||||
|
||||
inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip)
|
||||
{
|
||||
return _clipper_ln(ClipperLib::ctIntersection, subject, clip);
|
||||
return _clipper_ln(ctIntersection, subject, clip);
|
||||
}
|
||||
|
||||
inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::Polygons &clip)
|
||||
{
|
||||
Slic3r::Lines lines;
|
||||
lines.emplace_back(subject);
|
||||
return _clipper_ln(ClipperLib::ctIntersection, lines, clip);
|
||||
return _clipper_ln(ctIntersection, lines, clip);
|
||||
}
|
||||
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject);
|
||||
Slic3r::Polygons union_(const Slic3r::ExPolygons &subject);
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType);
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const PolyFillType fillType);
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2);
|
||||
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero);
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, PolyFillType fill_type = pftNonZero);
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject);
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2);
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject);
|
||||
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPolygons& poly2, bool safety_offset_ = false);
|
||||
|
||||
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.
|
||||
// If the contours are not intersecting, their orientation shall not be modified by union_pt().
|
||||
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject);
|
||||
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject);
|
||||
|
||||
Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
|
||||
Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject);
|
||||
|
||||
ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes);
|
||||
|
||||
// Implementing generalized loop (foreach) over a list of nodes which can be
|
||||
// ordered or unordered (performance gain) based on template parameter
|
||||
enum class e_ordering {
|
||||
ON,
|
||||
OFF
|
||||
};
|
||||
|
||||
// Create a template struct, template functions can not be partially specialized
|
||||
template<e_ordering o, class Fn> struct _foreach_node {
|
||||
void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn);
|
||||
};
|
||||
|
||||
// Specialization with NO ordering
|
||||
template<class Fn> struct _foreach_node<e_ordering::OFF, Fn> {
|
||||
void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn)
|
||||
{
|
||||
for (auto &n : nodes) fn(n);
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization with ordering
|
||||
template<class Fn> struct _foreach_node<e_ordering::ON, Fn> {
|
||||
void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn)
|
||||
{
|
||||
auto ordered_nodes = order_nodes(nodes);
|
||||
for (auto &n : nodes) fn(n);
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper function for the foreach_node which can deduce arguments automatically
|
||||
template<e_ordering o, class Fn>
|
||||
void foreach_node(const ClipperLib::PolyNodes &nodes, Fn &&fn)
|
||||
{
|
||||
_foreach_node<o, Fn>()(nodes, std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
// Collecting polygons of the tree into a list of Polygons, holes have clockwise
|
||||
// orientation.
|
||||
template<e_ordering ordering = e_ordering::OFF>
|
||||
void traverse_pt(const ClipperLib::PolyNode *tree, Polygons *out)
|
||||
{
|
||||
if (!tree) return; // terminates recursion
|
||||
|
||||
// Push the contour of the current level
|
||||
out->emplace_back(tree->Contour);
|
||||
|
||||
// Do the recursion for all the children.
|
||||
traverse_pt<ordering>(tree->Childs, out);
|
||||
}
|
||||
|
||||
// Collecting polygons of the tree into a list of ExPolygons.
|
||||
template<e_ordering ordering = e_ordering::OFF>
|
||||
void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *out)
|
||||
{
|
||||
if (!tree) return;
|
||||
else if(tree->IsHole()) {
|
||||
// Levels of holes are skipped and handled together with the
|
||||
// contour levels.
|
||||
traverse_pt<ordering>(tree->Childs, out);
|
||||
return;
|
||||
}
|
||||
|
||||
ExPolygon level;
|
||||
level.contour.points = tree->Contour;
|
||||
|
||||
foreach_node<ordering>(tree->Childs,
|
||||
[out, &level] (const ClipperLib::PolyNode *node) {
|
||||
|
||||
// Holes are collected here.
|
||||
level.holes.emplace_back(node->Contour);
|
||||
|
||||
// By doing a recursion, a new level expoly is created with the contour
|
||||
// and holes of the lower level. Doing this for all the childs.
|
||||
traverse_pt<ordering>(node->Childs, out);
|
||||
});
|
||||
|
||||
out->emplace_back(level);
|
||||
}
|
||||
|
||||
template<e_ordering o = e_ordering::OFF, class ExOrJustPolygons>
|
||||
void traverse_pt(const ClipperLib::PolyNodes &nodes, ExOrJustPolygons *retval)
|
||||
{
|
||||
foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) {
|
||||
traverse_pt<o>(node, retval);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* OTHER */
|
||||
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject);
|
||||
Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject);
|
||||
|
||||
Polygons top_level_islands(const Slic3r::Polygons &polygons);
|
||||
// Top level expolygons of the even-odd union, the ones nested in their holes go to `nested`.
|
||||
ExPolygons top_level_expolygons(const ExPolygons &expolygons, ExPolygons *nested = nullptr);
|
||||
|
||||
ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector<float> &deltas, double miter_limit);
|
||||
Points mittered_offset_path_scaled(const Points &contour, const std::vector<float> &deltas, double miter_limit);
|
||||
Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "ClipperZUtils.hpp"
|
||||
|
||||
#include <clipper2/clipper2_z.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace ClipperZUtils {
|
||||
|
||||
namespace C2 = Clipper2Lib_Z;
|
||||
|
||||
static C2::Paths64 to_paths64(const ZPaths &paths)
|
||||
{
|
||||
C2::Paths64 out;
|
||||
out.reserve(paths.size());
|
||||
for (const ZPath &path : paths) {
|
||||
C2::Path64 &dst = out.emplace_back();
|
||||
dst.reserve(path.size());
|
||||
for (const ZPoint &pt : path)
|
||||
dst.emplace_back(pt.x(), pt.y(), pt.z());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void append_zpaths(const C2::Paths64 &paths, ZPaths &out)
|
||||
{
|
||||
out.reserve(out.size() + paths.size());
|
||||
for (const C2::Path64 &path : paths) {
|
||||
ZPath &dst = out.emplace_back();
|
||||
dst.reserve(path.size());
|
||||
for (const C2::Point64 &pt : path)
|
||||
dst.emplace_back(pt.x, pt.y, pt.z);
|
||||
}
|
||||
}
|
||||
|
||||
double area(const ZPath &path)
|
||||
{
|
||||
const size_t size = path.size();
|
||||
if (size < 3)
|
||||
return 0.;
|
||||
double a = 0.;
|
||||
for (size_t i = 0, j = size - 1; i < size; j = i ++)
|
||||
a += (double(path[j].x()) + path[i].x()) * (double(path[j].y()) - path[i].y());
|
||||
return -a * 0.5;
|
||||
}
|
||||
|
||||
ZPaths clip_zpaths(ClipType clip_type, const ZPaths &subject, bool subject_open, const ZPaths &clip, const ZFillCallback &zfill, bool preserve_collinear)
|
||||
{
|
||||
C2::Clipper64 clipper;
|
||||
clipper.PreserveCollinear(preserve_collinear);
|
||||
if (zfill)
|
||||
clipper.SetZCallback([&zfill](const C2::Point64 &e1bot, const C2::Point64 &e1top, const C2::Point64 &e2bot, const C2::Point64 &e2top, C2::Point64 &pt) {
|
||||
// Clipper2 already copied Z from a coincident input vertex.
|
||||
auto coincident = [&pt](const C2::Point64 &p) { return p.x == pt.x && p.y == pt.y; };
|
||||
if (coincident(e1bot) || coincident(e1top) || coincident(e2bot) || coincident(e2top))
|
||||
return;
|
||||
ZPoint zpt(pt.x, pt.y, pt.z);
|
||||
zfill(ZPoint(e1bot.x, e1bot.y, e1bot.z), ZPoint(e1top.x, e1top.y, e1top.z),
|
||||
ZPoint(e2bot.x, e2bot.y, e2bot.z), ZPoint(e2top.x, e2top.y, e2top.z), zpt);
|
||||
pt.z = zpt.z();
|
||||
});
|
||||
if (subject_open)
|
||||
clipper.AddOpenSubject(to_paths64(subject));
|
||||
else
|
||||
clipper.AddSubject(to_paths64(subject));
|
||||
clipper.AddClip(to_paths64(clip));
|
||||
const C2::ClipType type = clip_type == ctIntersection ? C2::ClipType::Intersection :
|
||||
clip_type == ctUnion ? C2::ClipType::Union :
|
||||
clip_type == ctDifference ? C2::ClipType::Difference : C2::ClipType::Xor;
|
||||
C2::Paths64 closed, open;
|
||||
clipper.Execute(type, C2::FillRule::NonZero, closed, open);
|
||||
ZPaths out;
|
||||
append_zpaths(closed, out);
|
||||
append_zpaths(open, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace ClipperZUtils
|
||||
} // namespace Slic3r
|
||||
@@ -1,10 +1,11 @@
|
||||
#ifndef slic3r_ClipperZUtils_hpp_
|
||||
#define slic3r_ClipperZUtils_hpp_
|
||||
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include <clipper/clipper_z.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
@@ -12,10 +13,13 @@ namespace Slic3r {
|
||||
|
||||
namespace ClipperZUtils {
|
||||
|
||||
using ZPoint = ClipperLib_Z::IntPoint;
|
||||
using ZPoints = ClipperLib_Z::Path;
|
||||
using ZPath = ClipperLib_Z::Path;
|
||||
using ZPaths = ClipperLib_Z::Paths;
|
||||
using ZPoint = Vec3crd;
|
||||
using ZPoints = std::vector<ZPoint, PointsAllocator<ZPoint>>;
|
||||
using ZPath = ZPoints;
|
||||
using ZPaths = std::vector<ZPath, PointsAllocator<ZPath>>;
|
||||
|
||||
// Sets Z of an intersection point from the edges crossing there.
|
||||
using ZFillCallback = std::function<void(const ZPoint &e1bot, const ZPoint &e1top, const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt)>;
|
||||
|
||||
inline bool zpoint_lower(const ZPoint &l, const ZPoint &r)
|
||||
{
|
||||
@@ -101,6 +105,12 @@ inline VecOfPoints from_zpaths(const ZPaths &paths)
|
||||
return out;
|
||||
}
|
||||
|
||||
// Signed area, positive for a CCW path.
|
||||
double area(const ZPath &path);
|
||||
|
||||
// Non-zero rule boolean, the subject may be open. zfill only sees intersections off the input vertices.
|
||||
ZPaths clip_zpaths(ClipType clip_type, const ZPaths &subject, bool subject_open, const ZPaths &clip, const ZFillCallback &zfill, bool preserve_collinear = false);
|
||||
|
||||
class ClipperZIntersectionVisitor {
|
||||
public:
|
||||
using Intersection = std::pair<coord_t, coord_t>;
|
||||
@@ -126,8 +136,8 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
ClipperLib_Z::ZFillCallback clipper_callback() {
|
||||
return [this](const ZPoint &e1bot, const ZPoint &e1top,
|
||||
ZFillCallback clipper_callback() {
|
||||
return [this](const ZPoint &e1bot, const ZPoint &e1top,
|
||||
const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt)
|
||||
{ return (*this)(e1bot, e1top, e2bot, e2top, pt); };
|
||||
}
|
||||
|
||||
@@ -3519,7 +3519,7 @@ ExPolygon priv::to_expoly(const SurfacePatch &patch, const Project &projection,
|
||||
{
|
||||
Polygons polys = unproject_loops(patch, projection, depth_range);
|
||||
// should not be used when no opposit triangle are counted so should not create overlaps
|
||||
ClipperLib::PolyFillType fill_type = ClipperLib::PolyFillType::pftEvenOdd;
|
||||
PolyFillType fill_type = pftEvenOdd;
|
||||
ExPolygons expolys = Slic3r::union_ex(polys, fill_type);
|
||||
if (expolys.size() == 1)
|
||||
return expolys.front();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#include "clipper/clipper_z.hpp"
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
|
||||
@@ -404,18 +404,89 @@ bool Emboss::divide_segments_for_close_point(ExPolygons &expolygons, double dist
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clipper1's CleanPolygon(): removes vertices closer than `distance` to a neighbour or to the line through their neighbours.
|
||||
static void clean_polygon(Points &points, double distance)
|
||||
{
|
||||
auto points_are_close = [](const Point &a, const Point &b, double dist2) {
|
||||
const double dx = double(a.x() - b.x()), dy = double(a.y() - b.y());
|
||||
return dx * dx + dy * dy <= dist2;
|
||||
};
|
||||
auto distance_from_line2 = [](const Point &pt, const Point &ln1, const Point &ln2) {
|
||||
const double A = double(ln1.y() - ln2.y());
|
||||
const double B = double(ln2.x() - ln1.x());
|
||||
const double C = A * pt.x() + B * pt.y() - (A * ln1.x() + B * ln1.y());
|
||||
return (C * C) / (A * A + B * B);
|
||||
};
|
||||
auto slopes_near_collinear = [&distance_from_line2](const Point &pt1, const Point &pt2, const Point &pt3, double dist2) {
|
||||
if (std::abs(pt1.x() - pt2.x()) > std::abs(pt1.y() - pt2.y())) {
|
||||
if ((pt1.x() > pt2.x()) == (pt1.x() < pt3.x()))
|
||||
return distance_from_line2(pt1, pt2, pt3) < dist2;
|
||||
if ((pt2.x() > pt1.x()) == (pt2.x() < pt3.x()))
|
||||
return distance_from_line2(pt2, pt1, pt3) < dist2;
|
||||
return distance_from_line2(pt3, pt1, pt2) < dist2;
|
||||
}
|
||||
if ((pt1.y() > pt2.y()) == (pt1.y() < pt3.y()))
|
||||
return distance_from_line2(pt1, pt2, pt3) < dist2;
|
||||
if ((pt2.y() > pt1.y()) == (pt2.y() < pt3.y()))
|
||||
return distance_from_line2(pt2, pt1, pt3) < dist2;
|
||||
return distance_from_line2(pt3, pt1, pt2) < dist2;
|
||||
};
|
||||
|
||||
size_t size = points.size();
|
||||
if (size == 0)
|
||||
return;
|
||||
std::vector<size_t> next(size), prev(size);
|
||||
std::vector<char> done(size, 0);
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
next[i] = (i + 1) % size;
|
||||
prev[next[i]] = i;
|
||||
}
|
||||
auto exclude = [&next, &prev, &done](size_t op) {
|
||||
const size_t result = prev[op];
|
||||
next[result] = next[op];
|
||||
prev[next[op]] = result;
|
||||
done[result] = 0;
|
||||
return result;
|
||||
};
|
||||
const double dist2 = distance * distance;
|
||||
size_t op = 0;
|
||||
while (! done[op] && next[op] != prev[op]) {
|
||||
if (points_are_close(points[op], points[prev[op]], dist2)) {
|
||||
op = exclude(op);
|
||||
-- size;
|
||||
} else if (points_are_close(points[prev[op]], points[next[op]], dist2)) {
|
||||
exclude(next[op]);
|
||||
op = exclude(op);
|
||||
size -= 2;
|
||||
} else if (slopes_near_collinear(points[prev[op]], points[op], points[next[op]], dist2)) {
|
||||
op = exclude(op);
|
||||
-- size;
|
||||
} else {
|
||||
done[op] = 1;
|
||||
op = next[op];
|
||||
}
|
||||
}
|
||||
if (size < 3)
|
||||
size = 0;
|
||||
Points out;
|
||||
out.reserve(size);
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
out.emplace_back(points[op]);
|
||||
op = next[op];
|
||||
}
|
||||
points = std::move(out);
|
||||
}
|
||||
|
||||
HealedExPolygons Emboss::heal_polygons(const Polygons &shape, bool is_non_zero, unsigned int max_iteration)
|
||||
{
|
||||
const double clean_distance = 1.415; // little grater than sqrt(2)
|
||||
ClipperLib::PolyFillType fill_type = is_non_zero ?
|
||||
ClipperLib::pftNonZero : ClipperLib::pftEvenOdd;
|
||||
PolyFillType fill_type = is_non_zero ? pftNonZero : pftEvenOdd;
|
||||
|
||||
// When edit this code check that font 'ALIENATE.TTF' and glyph 'i' still work
|
||||
// fix of self intersections
|
||||
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygon.htm
|
||||
ClipperLib::Paths paths = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(shape), fill_type);
|
||||
ClipperLib::CleanPolygons(paths, clean_distance);
|
||||
Polygons polygons = to_polygons(paths);
|
||||
Polygons polygons = union_(shape, fill_type);
|
||||
for (Polygon &polygon : polygons)
|
||||
clean_polygon(polygon.points, clean_distance);
|
||||
polygons.erase(std::remove_if(polygons.begin(), polygons.end(),
|
||||
[](const Polygon &p) { return p.size() < 3; }), polygons.end());
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace Emboss
|
||||
/// Fix duplicit points and self intersections in polygons.
|
||||
/// Also try to reduce amount of points and remove useless polygon parts
|
||||
/// </summary>
|
||||
/// <param name="is_non_zero">Fill type ClipperLib::pftNonZero for overlapping otherwise </param>
|
||||
/// <param name="is_non_zero">Fill type pftNonZero for overlapping otherwise </param>
|
||||
/// <param name="max_iteration">Look at heal_expolygon()::max_iteration</param>
|
||||
/// <returns>Healed shapes with flag is fully healed</returns>
|
||||
HealedExPolygons heal_polygons(const Polygons &shape, bool is_non_zero = true, unsigned max_iteration = 10);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <cmath>
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../Clipper2Utils.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../Geometry/Circle.hpp"
|
||||
@@ -2734,11 +2733,8 @@ void multiline_fill(Polylines& polylines, const FillParams& params, float spacin
|
||||
|
||||
if (polylines.empty())
|
||||
return;
|
||||
// Convert source polylines to Clipper2 paths
|
||||
Clipper2Lib::Paths64 subject_paths = Slic3rPolylines_to_Paths64(polylines);
|
||||
|
||||
const double miter_limit = 2.0;
|
||||
const int rings = n_lines / 2;
|
||||
const int rings = n_lines / 2;
|
||||
|
||||
// Compute offsets (in units of spacing)
|
||||
std::vector<double> offsets;
|
||||
@@ -2757,10 +2753,6 @@ void multiline_fill(Polylines& polylines, const FillParams& params, float spacin
|
||||
offsets.push_back(start + i * spacing);
|
||||
}
|
||||
|
||||
// Process each offset
|
||||
Clipper2Lib::ClipperOffset offsetter(miter_limit);
|
||||
offsetter.AddPaths(subject_paths, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Round);
|
||||
|
||||
for (double t : offsets) {
|
||||
if (t == 0.0) {
|
||||
// Center line (only applies when n_lines is odd)
|
||||
@@ -2768,22 +2760,11 @@ void multiline_fill(Polylines& polylines, const FillParams& params, float spacin
|
||||
continue;
|
||||
}
|
||||
|
||||
// ClipperOffset with current offset distance (union is not needed here)
|
||||
Clipper2Lib::Paths64 offset_paths;
|
||||
offsetter.Execute(scale_(t), offset_paths);
|
||||
if (offset_paths.empty())
|
||||
continue;
|
||||
|
||||
// Convert back to polylines
|
||||
Polylines new_polylines = Paths64_to_polylines(offset_paths);
|
||||
|
||||
for (Polyline& pl : new_polylines) {
|
||||
if (pl.points.size() < 3)
|
||||
continue;
|
||||
if (pl.points.front() != pl.points.back())
|
||||
pl.points.push_back(pl.points.front());
|
||||
all_polylines.emplace_back(std::move(pl));
|
||||
}
|
||||
const float delta = float(scale_(t));
|
||||
// Arc tolerance of 1/500 of the offset, Clipper2's default.
|
||||
for (const Polygon &ring : offset(polylines, delta, jtRound, 0.002 * delta, etOpenRound))
|
||||
if (ring.size() >= 3)
|
||||
all_polylines.emplace_back(ring.split_at_first_point());
|
||||
}
|
||||
|
||||
polylines = std::move(all_polylines);
|
||||
|
||||
@@ -417,9 +417,9 @@ public:
|
||||
// bool sticks_removed =
|
||||
remove_sticks(polygons_src);
|
||||
// if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!";
|
||||
polygons_outer = aoffset1 == 0 ? to_polygons(polygons_src) : offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
|
||||
polygons_outer = aoffset1 == 0 ? to_polygons(polygons_src) : offset(polygons_src, float(aoffset1), jtMiter, miterLimit);
|
||||
if (aoffset2 < 0)
|
||||
polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit);
|
||||
polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), jtMiter, miterLimit);
|
||||
// Filter out contours with zero area or small area, contours with 2 points only.
|
||||
const double min_area_threshold = 0.01 * aoffset2 * aoffset2;
|
||||
remove_small(polygons_outer, min_area_threshold);
|
||||
|
||||
@@ -143,14 +143,7 @@ NodeSPtr Node::closestNode(const Point& loc)
|
||||
|
||||
bool inside(const Polygons &polygons, const Point &p)
|
||||
{
|
||||
int poly_count_inside = 0;
|
||||
for (const Polygon &poly : polygons) {
|
||||
const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly.points);
|
||||
if (is_inside_this_poly == -1)
|
||||
return true;
|
||||
poly_count_inside += is_inside_this_poly;
|
||||
}
|
||||
return (poly_count_inside % 2) == 1;
|
||||
return contains(polygons, p, true);
|
||||
}
|
||||
|
||||
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist)
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "TopExp_Explorer.hxx"
|
||||
#include "TopoDS.hxx"
|
||||
#include "BRepExtrema_SelfIntersection.hxx"
|
||||
#include "libslic3r/clipper.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -211,26 +210,15 @@ bool get_svg_profile(const char *path, std::vector<Element_Info> &element_infos,
|
||||
Polygons polygons;
|
||||
bool close_polygon = false;
|
||||
for (int i = 0; i < path_line_points.size(); ++i) {
|
||||
ClipperLib::Path pt_path;
|
||||
for (auto line_point : path_line_points[i]) {
|
||||
pt_path.push_back(ClipperLib::IntPoint(line_point.first.X() * scale_size, line_point.first.Y() * scale_size));
|
||||
Polyline pt_path;
|
||||
for (auto line_point : path_line_points[i]) {
|
||||
pt_path.points.push_back(Point(line_point.first.X() * scale_size, line_point.first.Y() * scale_size));
|
||||
}
|
||||
pt_path.push_back(ClipperLib::IntPoint(path_line_points[i].back().second.X() * scale_size, path_line_points[i].back().second.Y() * scale_size));
|
||||
pt_path.points.push_back(Point(path_line_points[i].back().second.X() * scale_size, path_line_points[i].back().second.Y() * scale_size));
|
||||
|
||||
ClipperLib::Paths out_paths;
|
||||
ClipperLib::ClipperOffset co;
|
||||
if (pt_path.front() == pt_path.back()) {
|
||||
co.AddPath(pt_path, ClipperLib::jtMiter, ClipperLib::etClosedLine);
|
||||
close_polygon = true;
|
||||
} else {
|
||||
co.AddPath(pt_path, ClipperLib::jtMiter, ClipperLib::etOpenSquare);
|
||||
close_polygon = false;
|
||||
}
|
||||
co.Execute(out_paths, stroke_width / 2);
|
||||
|
||||
for (auto out_path : out_paths) {
|
||||
polygons.emplace_back(Polygon(out_path));
|
||||
}
|
||||
close_polygon = pt_path.points.front() == pt_path.points.back();
|
||||
if (stroke_width > 0)
|
||||
append(polygons, offset(pt_path, stroke_width / 2, jtMiter, 2., close_polygon ? etClosedLine : etOpenSquare));
|
||||
}
|
||||
|
||||
if (!close_polygon)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "clipper.hpp"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
@@ -627,9 +627,9 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
}
|
||||
#else
|
||||
|
||||
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
|
||||
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
|
||||
#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
|
||||
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS jtMiter, 3.
|
||||
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS jtMiter, 1.5
|
||||
#define EXTERNAL_SURFACES_OFFSET_PARAMETERS jtSquare, 0.
|
||||
|
||||
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
|
||||
{
|
||||
|
||||
@@ -500,26 +500,26 @@ Polylines to_dashes(const Polyline &polyline, const DashesParam& param)
|
||||
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m)
|
||||
{
|
||||
// convert stroke to polygon
|
||||
ClipperLib::JoinType join_type = ClipperLib::JoinType::jtSquare;
|
||||
JoinType join_type = jtSquare;
|
||||
switch (static_cast<NSVGlineJoin>(shape.strokeLineJoin)) {
|
||||
case NSVGlineJoin::NSVG_JOIN_BEVEL: join_type = ClipperLib::JoinType::jtSquare; break;
|
||||
case NSVGlineJoin::NSVG_JOIN_MITER: join_type = ClipperLib::JoinType::jtMiter; break;
|
||||
case NSVGlineJoin::NSVG_JOIN_ROUND: join_type = ClipperLib::JoinType::jtRound; break;
|
||||
case NSVGlineJoin::NSVG_JOIN_BEVEL: join_type = jtSquare; break;
|
||||
case NSVGlineJoin::NSVG_JOIN_MITER: join_type = jtMiter; break;
|
||||
case NSVGlineJoin::NSVG_JOIN_ROUND: join_type = jtRound; break;
|
||||
}
|
||||
|
||||
double mitter = shape.miterLimit * param.scale;
|
||||
if (join_type == ClipperLib::JoinType::jtRound) {
|
||||
if (join_type == jtRound) {
|
||||
// mitter is used as ArcTolerance
|
||||
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
|
||||
mitter = std::pow(param.tesselation_tolerance, 1/3.);
|
||||
}
|
||||
float stroke_width = static_cast<float>(shape.strokeWidth * param.scale);
|
||||
|
||||
ClipperLib::EndType end_type = ClipperLib::EndType::etOpenButt;
|
||||
EndType end_type = etOpenButt;
|
||||
switch (static_cast<NSVGlineCap>(shape.strokeLineCap)) {
|
||||
case NSVGlineCap::NSVG_CAP_BUTT: end_type = ClipperLib::EndType::etOpenButt; break;
|
||||
case NSVGlineCap::NSVG_CAP_ROUND: end_type = ClipperLib::EndType::etOpenRound; break;
|
||||
case NSVGlineCap::NSVG_CAP_SQUARE: end_type = ClipperLib::EndType::etOpenSquare; break;
|
||||
case NSVGlineCap::NSVG_CAP_BUTT: end_type = etOpenButt; break;
|
||||
case NSVGlineCap::NSVG_CAP_ROUND: end_type = etOpenRound; break;
|
||||
case NSVGlineCap::NSVG_CAP_SQUARE: end_type = etOpenSquare; break;
|
||||
}
|
||||
|
||||
Polygons result;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "AABBTreeLines.hpp"
|
||||
#include "BridgeDetector.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "ClipperZUtils.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "Feature/FuzzySkin/FuzzySkin.hpp"
|
||||
@@ -296,13 +297,12 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
||||
return out;
|
||||
}
|
||||
|
||||
static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, const ClipperLib_Z::Paths& clip, ClipperLib_Z::ClipType clipType)
|
||||
static ClipperZUtils::ZPaths clip_extrusion(const ClipperZUtils::ZPath& subject, const ClipperZUtils::ZPaths& clip, ClipType clipType)
|
||||
{
|
||||
ClipperLib_Z::Clipper clipper;
|
||||
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, const ClipperLib_Z::IntPoint& e2bot,
|
||||
const ClipperLib_Z::IntPoint& e2top, ClipperLib_Z::IntPoint& pt) {
|
||||
ClipperLib_Z::IntPoint start = e1bot;
|
||||
ClipperLib_Z::IntPoint end = e1top;
|
||||
auto zfill = [](const ClipperZUtils::ZPoint& e1bot, const ClipperZUtils::ZPoint& e1top, const ClipperZUtils::ZPoint& e2bot,
|
||||
const ClipperZUtils::ZPoint& e2top, ClipperZUtils::ZPoint& pt) {
|
||||
ClipperZUtils::ZPoint start = e1bot;
|
||||
ClipperZUtils::ZPoint end = e1top;
|
||||
|
||||
if (start.z() <= 0 && end.z() <= 0) {
|
||||
start = e2bot;
|
||||
@@ -317,23 +317,15 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, con
|
||||
double t = std::sqrt(dist_sqr / length_sqr);
|
||||
|
||||
pt.z() = start.z() + coord_t((end.z() - start.z()) * t);
|
||||
});
|
||||
};
|
||||
|
||||
clipper.AddPath(subject, ClipperLib_Z::ptSubject, false);
|
||||
clipper.AddPaths(clip, ClipperLib_Z::ptClip, true);
|
||||
|
||||
ClipperLib_Z::Paths clipped_paths;
|
||||
{
|
||||
ClipperLib_Z::PolyTree clipped_polytree;
|
||||
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), clipped_paths);
|
||||
}
|
||||
ClipperZUtils::ZPaths clipped_paths = ClipperZUtils::clip_zpaths(clipType, ClipperZUtils::ZPaths{ subject }, true, clip, zfill);
|
||||
|
||||
// Clipped path could contain vertices from the clip with a Z coordinate equal to zero.
|
||||
// For those vertices, we must assign value based on the subject.
|
||||
// This happens only in sporadic cases.
|
||||
for (ClipperLib_Z::Path& path : clipped_paths)
|
||||
for (ClipperLib_Z::IntPoint& c_pt : path)
|
||||
for (ClipperZUtils::ZPath& path : clipped_paths)
|
||||
for (ClipperZUtils::ZPoint& c_pt : path)
|
||||
if (c_pt.z() == 0) {
|
||||
// Now we must find the corresponding line on with this point is located and compute line width (Z coordinate).
|
||||
if (subject.size() <= 2)
|
||||
@@ -366,8 +358,8 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, con
|
||||
}
|
||||
|
||||
assert([&clipped_paths = std::as_const(clipped_paths)]() -> bool {
|
||||
for (const ClipperLib_Z::Path& path : clipped_paths)
|
||||
for (const ClipperLib_Z::IntPoint& pt : path)
|
||||
for (const ClipperZUtils::ZPath& path : clipped_paths)
|
||||
for (const ClipperZUtils::ZPoint& pt : path)
|
||||
if (pt.z() <= 0)
|
||||
return false;
|
||||
return true;
|
||||
@@ -376,7 +368,7 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, con
|
||||
return clipped_paths;
|
||||
}
|
||||
|
||||
static double clipper_z_path_length(const ClipperLib_Z::Path &path)
|
||||
static double clipper_z_path_length(const ClipperZUtils::ZPath &path)
|
||||
{
|
||||
double len = 0.;
|
||||
for (size_t i = 1; i < path.size(); ++ i)
|
||||
@@ -413,7 +405,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
ExtrusionPaths paths;
|
||||
// detect overhanging/bridging perimeters
|
||||
if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) {
|
||||
ClipperLib_Z::Path extrusion_path;
|
||||
ClipperZUtils::ZPath extrusion_path;
|
||||
extrusion_path.reserve(extrusion->size());
|
||||
BoundingBox extrusion_path_bbox;
|
||||
for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) {
|
||||
@@ -421,7 +413,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
extrusion_path_bbox.merge(Point(ej.p.x(), ej.p.y()));
|
||||
}
|
||||
|
||||
ClipperLib_Z::Paths lower_slices_paths;
|
||||
ClipperZUtils::ZPaths lower_slices_paths;
|
||||
{
|
||||
lower_slices_paths.reserve(perimeter_generator.lower_slices_polygons().size());
|
||||
Points clipped;
|
||||
@@ -431,7 +423,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
ClipperUtils::clip_clipper_polygon_with_subject_bbox(poly.points, extrusion_path_bbox, clipped);
|
||||
if (!clipped.empty()) {
|
||||
lower_slices_paths.emplace_back();
|
||||
ClipperLib_Z::Path &out = lower_slices_paths.back();
|
||||
ClipperZUtils::ZPath &out = lower_slices_paths.back();
|
||||
out.reserve(clipped.size());
|
||||
for (const Point &pt : clipped)
|
||||
out.emplace_back(pt.x(), pt.y(), 0);
|
||||
@@ -440,7 +432,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
}
|
||||
|
||||
// get non-overhang paths by intersecting this loop with the grown lower slices
|
||||
extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctIntersection), role,
|
||||
extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ctIntersection), role,
|
||||
is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow);
|
||||
|
||||
// Always reverse extrusion if use fuzzy skin: https://github.com/OrcaSlicer/OrcaSlicer/pull/2413#issuecomment-1769735357
|
||||
@@ -481,7 +473,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
// get overhang paths by checking what parts of this loop fall
|
||||
// outside the grown lower slices (thus where the distance between
|
||||
// the loop centerline and original lower slices is >= half nozzle diameter
|
||||
extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctDifference), erOverhangPerimeter,
|
||||
extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ctDifference), erOverhangPerimeter,
|
||||
perimeter_generator.overhang_flow);
|
||||
|
||||
// Reapply the nearest point search for starting point.
|
||||
@@ -674,10 +666,10 @@ static void clip_inner_walls_over_top(std::vector<Arachne::VariableWidthLines> &
|
||||
};
|
||||
// Pull the cut back by half a wall width: the clip severs the centerline, but the bead's rounded end
|
||||
// extends half a width past its endpoint and would otherwise overlap the top fill.
|
||||
ClipperLib_Z::Paths top_paths_z;
|
||||
ClipperZUtils::ZPaths top_paths_z;
|
||||
for (const Polygon &poly : to_polygons(offset_ex(top_region, float(perimeter_width) / 2.f))) {
|
||||
top_paths_z.emplace_back();
|
||||
ClipperLib_Z::Path &out = top_paths_z.back();
|
||||
ClipperZUtils::ZPath &out = top_paths_z.back();
|
||||
out.reserve(poly.points.size());
|
||||
for (const Point &pt : poly.points)
|
||||
out.emplace_back(pt.x(), pt.y(), 0);
|
||||
@@ -695,21 +687,21 @@ static void clip_inner_walls_over_top(std::vector<Arachne::VariableWidthLines> &
|
||||
}
|
||||
if (overlap == TopOverlap::Full)
|
||||
continue; // the clip below would return nothing anyway
|
||||
ClipperLib_Z::Path subject;
|
||||
ClipperZUtils::ZPath subject;
|
||||
subject.reserve(el.size());
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions)
|
||||
subject.emplace_back(j.p.x(), j.p.y(), j.w);
|
||||
ClipperLib_Z::Paths pieces = clip_extrusion(subject, top_paths_z, ClipperLib_Z::ctDifference);
|
||||
ClipperZUtils::ZPaths pieces = clip_extrusion(subject, top_paths_z, ctDifference);
|
||||
|
||||
// Clipper treats the subject as an open polyline, so it also cuts a closed loop at its (arbitrary)
|
||||
// start vertex and may reverse pieces. Stitch pieces sharing an endpoint back together.
|
||||
auto same_pt = [](const ClipperLib_Z::IntPoint &p, const ClipperLib_Z::IntPoint &q) {
|
||||
auto same_pt = [](const ClipperZUtils::ZPoint &p, const ClipperZUtils::ZPoint &q) {
|
||||
return std::abs(p.x() - q.x()) <= SCALED_EPSILON && std::abs(p.y() - q.y()) <= SCALED_EPSILON;
|
||||
};
|
||||
for (size_t i = 0; i < pieces.size(); ++ i) {
|
||||
for (size_t j = i + 1; j < pieces.size();) {
|
||||
ClipperLib_Z::Path &a = pieces[i];
|
||||
ClipperLib_Z::Path &b = pieces[j];
|
||||
ClipperZUtils::ZPath &a = pieces[i];
|
||||
ClipperZUtils::ZPath &b = pieces[j];
|
||||
if (same_pt(a.front(), b.front()) || same_pt(a.front(), b.back()))
|
||||
std::reverse(a.begin(), a.end());
|
||||
if (same_pt(a.back(), b.back()))
|
||||
@@ -726,7 +718,7 @@ static void clip_inner_walls_over_top(std::vector<Arachne::VariableWidthLines> &
|
||||
// If the clip removed next to nothing, keep the loop untouched instead of slitting it open. The
|
||||
// half-width pull-back above already costs about one width per crossing, hence two widths.
|
||||
double kept_length = 0.;
|
||||
for (const ClipperLib_Z::Path &path : pieces)
|
||||
for (const ClipperZUtils::ZPath &path : pieces)
|
||||
kept_length += clipper_z_path_length(path);
|
||||
if (clipper_z_path_length(subject) - kept_length < 2. * double(perimeter_width)) {
|
||||
append(kept_over_top, covered_by(el));
|
||||
@@ -734,10 +726,10 @@ static void clip_inner_walls_over_top(std::vector<Arachne::VariableWidthLines> &
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const ClipperLib_Z::Path &path : pieces) {
|
||||
for (const ClipperZUtils::ZPath &path : pieces) {
|
||||
Arachne::ExtrusionLine clipped(el.inset_idx, el.is_odd);
|
||||
clipped.junctions.reserve(path.size());
|
||||
for (const ClipperLib_Z::IntPoint &pt : path)
|
||||
for (const ClipperZUtils::ZPoint &pt : path)
|
||||
clipped.junctions.emplace_back(Point(pt.x(), pt.y()), coord_t(pt.z()), el.inset_idx);
|
||||
// Discard tiny leftovers that would print as zits.
|
||||
if (clipped.size() >= 2 && clipped.getLength() >= perimeter_width)
|
||||
@@ -1056,7 +1048,7 @@ ExtrusionPaths sort_extra_perimeters(const ExtrusionPaths& extra_perims, int ind
|
||||
return filtered;
|
||||
}
|
||||
|
||||
#define EXTRA_PERIMETER_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
|
||||
#define EXTRA_PERIMETER_OFFSET_PARAMETERS jtSquare, 0.
|
||||
// #define EXTRA_PERIM_DEBUG_FILES
|
||||
// Function will generate extra perimeters clipped over nonbridgeable areas of the provided surface and returns both the new perimeters and
|
||||
// Polygons filled by those clipped perimeters
|
||||
|
||||
@@ -70,7 +70,7 @@ double Polygon::area() const
|
||||
|
||||
bool Polygon::is_counter_clockwise() const
|
||||
{
|
||||
return ClipperLib::Orientation(this->points);
|
||||
return this->area() >= 0.;
|
||||
}
|
||||
|
||||
bool Polygon::is_clockwise() const
|
||||
@@ -678,7 +678,7 @@ void remove_collinear(Polygons &polys)
|
||||
remove_collinear(poly);
|
||||
}
|
||||
|
||||
Polygons polygons_simplify(const Polygons &source_polygons, double tolerance, bool strictly_simple /* = true */)
|
||||
Polygons polygons_simplify(const Polygons &source_polygons, double tolerance)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(source_polygons.size());
|
||||
@@ -687,13 +687,15 @@ Polygons polygons_simplify(const Polygons &source_polygons, double tolerance, bo
|
||||
Points simplified = MultiPoint::_douglas_peucker(to_polyline(source_polygon).points, tolerance);
|
||||
// then remove the last (repeated) point.
|
||||
simplified.pop_back();
|
||||
// Simplify the decimated contour by ClipperLib.
|
||||
bool ccw = ClipperLib::Area(simplified) > 0.;
|
||||
for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero, strictly_simple)) {
|
||||
// Simplify the decimated contour by a union.
|
||||
bool ccw = Polygon::area(simplified) > 0.;
|
||||
Polygons decimated(1);
|
||||
decimated.front().points = std::move(simplified);
|
||||
for (Polygon &polygon : union_(decimated)) {
|
||||
if (! ccw)
|
||||
// ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW.
|
||||
std::reverse(path.begin(), path.end());
|
||||
out.emplace_back(std::move(path));
|
||||
// The union reorients negative area contours to become positive. Reverse holes back to CW.
|
||||
polygon.reverse();
|
||||
out.emplace_back(std::move(polygon));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
@@ -728,9 +730,40 @@ bool overlaps(const Polygons& polys1, const Polygons& polys2)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clipper1's PointInPolygon(): 1 inside, 0 outside, -1 on the boundary.
|
||||
static int point_in_polygon(const Point &pt, const Points &path)
|
||||
{
|
||||
int result = 0;
|
||||
size_t cnt = path.size();
|
||||
if (cnt < 3) return 0;
|
||||
Point ip = path[0];
|
||||
for (size_t i = 1; i <= cnt; ++i) {
|
||||
Point ipNext = (i == cnt ? path[0] : path[i]);
|
||||
if (ipNext.y() == pt.y() && ((ipNext.x() == pt.x()) || (ip.y() == pt.y() && ((ipNext.x() > pt.x()) == (ip.x() < pt.x())))))
|
||||
return -1;
|
||||
if ((ip.y() < pt.y()) != (ipNext.y() < pt.y())) {
|
||||
if (ip.x() >= pt.x()) {
|
||||
if (ipNext.x() > pt.x())
|
||||
result = 1 - result;
|
||||
else {
|
||||
int64_t d = int64_t(ip.x() - pt.x()) * int64_t(ipNext.y() - pt.y()) - int64_t(ipNext.x() - pt.x()) * int64_t(ip.y() - pt.y());
|
||||
if (! d) return -1;
|
||||
if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result;
|
||||
}
|
||||
} else if (ipNext.x() > pt.x()) {
|
||||
int64_t d = int64_t(ip.x() - pt.x()) * int64_t(ipNext.y() - pt.y()) - int64_t(ipNext.x() - pt.x()) * int64_t(ip.y() - pt.y());
|
||||
if (! d) return -1;
|
||||
if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result;
|
||||
}
|
||||
}
|
||||
ip = ipNext;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool contains(const Polygon &polygon, const Point &p, bool border_result)
|
||||
{
|
||||
if (const int poly_count_inside = ClipperLib::PointInPolygon(p, polygon.points);
|
||||
if (const int poly_count_inside = point_in_polygon(p, polygon.points);
|
||||
poly_count_inside == -1)
|
||||
return border_result;
|
||||
else
|
||||
@@ -741,7 +774,7 @@ bool contains(const Polygons &polygons, const Point &p, bool border_result)
|
||||
{
|
||||
int poly_count_inside = 0;
|
||||
for (const Polygon &poly : polygons) {
|
||||
const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly.points);
|
||||
const int is_inside_this_poly = point_in_polygon(p, poly.points);
|
||||
if (is_inside_this_poly == -1)
|
||||
return border_result;
|
||||
poly_count_inside += is_inside_this_poly;
|
||||
|
||||
@@ -159,7 +159,7 @@ inline void polygons_append(Polygons &dst, Polygons &&src)
|
||||
}
|
||||
}
|
||||
|
||||
Polygons polygons_simplify(const Polygons &polys, double tolerance, bool strictly_simple = true);
|
||||
Polygons polygons_simplify(const Polygons &polys, double tolerance);
|
||||
|
||||
inline void polygons_rotate(Polygons &polys, double angle)
|
||||
{
|
||||
|
||||
@@ -3489,7 +3489,7 @@ void Print::_make_skirt()
|
||||
Polygon loop;
|
||||
{
|
||||
// Orca: the hull already represents the occupied outline used for this skirt.
|
||||
Polygons loops = offset(hull, distance, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
Polygons loops = offset(hull, distance, jtRound, float(scale_(0.1)));
|
||||
Geometry::simplify_polygons(loops, scale_(0.05), &loops);
|
||||
if (loops.empty())
|
||||
break;
|
||||
@@ -3526,7 +3526,7 @@ void Print::_make_skirt()
|
||||
}
|
||||
|
||||
if (collect_skirt_hull)
|
||||
for (Polygon &poly : offset(hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1))))
|
||||
for (Polygon &poly : offset(hull, distance + 0.5f * float(scale_(spacing)), jtRound, float(scale_(0.1))))
|
||||
append(m_skirt_convex_hull, std::move(poly.points));
|
||||
};
|
||||
|
||||
@@ -3632,7 +3632,7 @@ void Print::_make_skirt()
|
||||
if (group.emits_skirt) {
|
||||
// Orca: If the expanded skirt outline touches another group
|
||||
// or obstacle, merge them and run the pass again.
|
||||
Polygons envelopes = offset(envelope, grouping_offset, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
Polygons envelopes = offset(envelope, grouping_offset, jtRound, float(scale_(0.1)));
|
||||
if (envelopes.empty())
|
||||
continue;
|
||||
envelope = std::move(envelopes.front());
|
||||
|
||||
@@ -251,9 +251,9 @@ enum class PrintOrder
|
||||
|
||||
enum class SlicingMode
|
||||
{
|
||||
// Regular, applying ClipperLib::pftNonZero rule when creating ExPolygons.
|
||||
// Regular, applying pftNonZero rule when creating ExPolygons.
|
||||
Regular,
|
||||
// Compatible with 3DLabPrint models, applying ClipperLib::pftEvenOdd rule when creating ExPolygons.
|
||||
// Compatible with 3DLabPrint models, applying pftEvenOdd rule when creating ExPolygons.
|
||||
EvenOdd,
|
||||
// Orienting all contours CCW, thus closing all holes.
|
||||
CloseHoles,
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "Print.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Clipper2Utils.hpp"
|
||||
#include "ElephantFootCompensation.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "I18N.hpp"
|
||||
@@ -56,7 +55,7 @@ using namespace std::literals;
|
||||
// #define PRINT_OBJECT_TIMING
|
||||
|
||||
#ifdef PRINT_OBJECT_TIMING
|
||||
// time limit for one ClipperLib operation (union / diff / offset), in ms
|
||||
// time limit for one Clipper operation (union / diff / offset), in ms
|
||||
#define PRINT_OBJECT_TIME_LIMIT_DEFAULT 50
|
||||
#include <boost/current_function.hpp>
|
||||
#include "Timer.hpp"
|
||||
@@ -1851,7 +1850,7 @@ void PrintObject::detect_surfaces_type()
|
||||
|
||||
// Grow, then keep only what the configured direction allows, using the top's own filled
|
||||
// outline (same outer edge, holes closed) to tell the two apart.
|
||||
ExPolygons expanded = offset_ex_2(island_top, d, Clipper2Lib::JoinType::Miter);
|
||||
ExPolygons expanded = offset_ex(island_top, float(d), jtMiter, 2.);
|
||||
if (direction != TopSurfaceExpansionDirection::InwardAndOutward) {
|
||||
ExPolygons outline;
|
||||
outline.reserve(island_top.size());
|
||||
@@ -2558,9 +2557,9 @@ void PrintObject::discover_vertical_shells()
|
||||
// Open to remove (filter out) regions narrower than an infill extrusion line width.
|
||||
-narrow_ensure_vertical_wall_thickness_region_radius,
|
||||
// Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill.
|
||||
narrow_ensure_vertical_wall_thickness_region_radius + narrow_sparse_infill_region_radius, ClipperLib::jtSquare),
|
||||
narrow_ensure_vertical_wall_thickness_region_radius + narrow_sparse_infill_region_radius, jtSquare),
|
||||
// Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions.
|
||||
narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare);
|
||||
narrow_sparse_infill_region_radius - tiny_overlap_radius, jtSquare);
|
||||
|
||||
Polygons object_volume;
|
||||
Polygons internal_volume;
|
||||
@@ -4170,7 +4169,7 @@ void PrintObject::clip_fill_surfaces()
|
||||
upper_internal = intersection(
|
||||
// Regularize the overhang regions, so that the infill areas will not become excessively jagged.
|
||||
smooth_outward(
|
||||
closing(upper_internal, closing_radius, ClipperLib::jtSquare, 0.),
|
||||
closing(upper_internal, closing_radius, jtSquare, 0.),
|
||||
scaled<coord_t>(0.1)),
|
||||
lower_layer_internal_surfaces);
|
||||
// Apply new internal infill to regions.
|
||||
@@ -4341,7 +4340,7 @@ void PrintObject::discover_horizontal_shells()
|
||||
// have the same angle, so the next shell would be grown even more and so on.
|
||||
Polygons too_narrow = diff(
|
||||
new_internal_solid,
|
||||
opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5));
|
||||
opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5));
|
||||
if (! too_narrow.empty()) {
|
||||
// grow the collapsing parts and add the extra area to the neighbor layer
|
||||
// as well as to our original surfaces so that we support this
|
||||
@@ -4543,7 +4542,7 @@ void PrintObject::_generate_support_material()
|
||||
}
|
||||
|
||||
// BBS
|
||||
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
|
||||
#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtSquare, 0.
|
||||
#define SUPPORT_MATERIAL_MARGIN 1.2
|
||||
template<typename PolysType>
|
||||
void PrintObject::remove_bridges_from_contacts(
|
||||
|
||||
@@ -135,7 +135,7 @@ ExPolygons offset_waffle_style_ex(const ConcaveHull &hull, coord_t delta)
|
||||
Polygons offset_waffle_style(const ConcaveHull &hull, coord_t delta)
|
||||
{
|
||||
auto arc_tolerance = scaled<double>(0.01);
|
||||
Polygons res = closing(hull.polygons(), 2 * delta, delta, ClipperLib::jtRound, arc_tolerance);
|
||||
Polygons res = closing(hull.polygons(), 2 * delta, delta, jtRound, arc_tolerance);
|
||||
|
||||
auto it = std::remove_if(res.begin(), res.end(), [](Polygon &p) { return p.is_clockwise(); });
|
||||
res.erase(it, res.end());
|
||||
|
||||
@@ -171,24 +171,8 @@ struct PadSkeleton { ExPolygons inner, outer; };
|
||||
|
||||
PadSkeleton divide_blueprint(const ExPolygons &bp)
|
||||
{
|
||||
ClipperLib::PolyTree ptree = union_pt(bp);
|
||||
|
||||
PadSkeleton ret;
|
||||
ret.inner.reserve(size_t(ptree.Total()));
|
||||
ret.outer.reserve(size_t(ptree.Total()));
|
||||
|
||||
for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) {
|
||||
ExPolygon poly;
|
||||
poly.contour.points = std::move(node->Contour);
|
||||
for (ClipperLib::PolyTree::PolyNode *child : node->Childs) {
|
||||
poly.holes.emplace_back(std::move(child->Contour));
|
||||
|
||||
traverse_pt(child->Childs, &ret.inner);
|
||||
}
|
||||
|
||||
ret.outer.emplace_back(poly);
|
||||
}
|
||||
|
||||
ret.outer = top_level_expolygons(bp, &ret.inner);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -257,7 +241,7 @@ public:
|
||||
auto model_bp_offs =
|
||||
offset_ex(model_blueprint,
|
||||
scaled<float>(cfg.embed_object.object_gap_mm),
|
||||
ClipperLib::jtMiter, 1);
|
||||
jtMiter, 1);
|
||||
|
||||
ExPolygons fullcvh =
|
||||
wafflized_concave_hull(support_blueprint, model_bp_offs, cfg, thr);
|
||||
|
||||
@@ -186,8 +186,8 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
|
||||
// Produce 2 bands around the island, a safe band for dangling overhangs
|
||||
// and an unsafe band for sloped overhangs.
|
||||
// These masks include the original island
|
||||
auto dangl_mask = expand(bottom_polygons, between_layers_offset, ClipperLib::jtSquare);
|
||||
auto overh_mask = expand(bottom_polygons, slope_offset, ClipperLib::jtSquare);
|
||||
auto dangl_mask = expand(bottom_polygons, between_layers_offset, jtSquare);
|
||||
auto overh_mask = expand(bottom_polygons, slope_offset, jtSquare);
|
||||
|
||||
// Absolutely hopeless overhangs are those outside the unsafe band
|
||||
top.overhangs = diff_ex(*top.polygon, overh_mask);
|
||||
|
||||
@@ -230,15 +230,15 @@ void SVG::draw(const Points &points, std::string fill, coord_t radius)
|
||||
this->draw(*it, fill, radius);
|
||||
}
|
||||
|
||||
void SVG::draw(const ClipperLib::Path &polygon, double scale, std::string stroke, coordf_t stroke_width)
|
||||
void SVG::draw(const Points &polygon, double scale, std::string stroke, coordf_t stroke_width)
|
||||
{
|
||||
this->stroke = stroke;
|
||||
this->path(this->get_path_d(polygon, scale, true), false, stroke_width, 1.f);
|
||||
}
|
||||
|
||||
void SVG::draw(const ClipperLib::Paths &polygons, double scale, std::string stroke, coordf_t stroke_width)
|
||||
void SVG::draw(const VecOfPoints &polygons, double scale, std::string stroke, coordf_t stroke_width)
|
||||
{
|
||||
for (ClipperLib::Paths::const_iterator it = polygons.begin(); it != polygons.end(); ++ it)
|
||||
for (VecOfPoints::const_iterator it = polygons.begin(); it != polygons.end(); ++ it)
|
||||
draw(*it, scale, stroke, stroke_width);
|
||||
}
|
||||
|
||||
@@ -284,11 +284,11 @@ std::string SVG::get_path_d(const MultiPoint &mp, bool closed) const
|
||||
return d.str();
|
||||
}
|
||||
|
||||
std::string SVG::get_path_d(const ClipperLib::Path &path, double scale, bool closed) const
|
||||
std::string SVG::get_path_d(const Points &path, double scale, bool closed) const
|
||||
{
|
||||
std::ostringstream d;
|
||||
d << "M ";
|
||||
for (ClipperLib::Path::const_iterator p = path.begin(); p != path.end(); ++p) {
|
||||
for (Points::const_iterator p = path.begin(); p != path.end(); ++p) {
|
||||
d << to_svg_x(scale * p->x() - origin(0)) << " ";
|
||||
d << to_svg_y(scale * p->y() - origin(1)) << " ";
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define slic3r_SVG_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "clipper.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
@@ -70,9 +69,9 @@ public:
|
||||
void draw(const Point &point, std::string fill = "black", coord_t radius = 0);
|
||||
void draw(const Points &points, std::string fill = "black", coord_t radius = 0);
|
||||
|
||||
// Support for rendering the ClipperLib paths
|
||||
void draw(const ClipperLib::Path &polygon, double scale, std::string fill = "grey", coordf_t stroke_width = 0);
|
||||
void draw(const ClipperLib::Paths &polygons, double scale, std::string fill = "grey", coordf_t stroke_width = 0);
|
||||
// Paths drawn with their coordinates multiplied by scale.
|
||||
void draw(const Points &polygon, double scale, std::string fill = "grey", coordf_t stroke_width = 0);
|
||||
void draw(const VecOfPoints &polygons, double scale, std::string fill = "grey", coordf_t stroke_width = 0);
|
||||
|
||||
void draw_text(const Point &pt, const char *text, const char *color, int font_size = 20);
|
||||
void draw_legend(const Point &pt, const char *text, const char *color);
|
||||
@@ -88,7 +87,7 @@ public:
|
||||
|
||||
void path(const std::string &d, bool fill, coordf_t stroke_width, const float fill_opacity);
|
||||
std::string get_path_d(const MultiPoint &mp, bool closed = false) const;
|
||||
std::string get_path_d(const ClipperLib::Path &mp, double scale, bool closed = false) const;
|
||||
std::string get_path_d(const Points &mp, double scale, bool closed = false) const;
|
||||
|
||||
public:
|
||||
static void export_expolygons(const char *path, const BoundingBox &bbox, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#undef assert
|
||||
#endif
|
||||
|
||||
#include "clipper.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
@@ -2041,22 +2040,6 @@ Polylines chain_polylines(Polylines &&polylines, const Point *start_near)
|
||||
return out;
|
||||
}
|
||||
|
||||
template<class T> static inline T chain_path_items(const Points &points, const T &items)
|
||||
{
|
||||
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), nullptr);
|
||||
T out;
|
||||
out.reserve(items.size());
|
||||
for (auto &segment_and_reversal : ordered)
|
||||
out.emplace_back(items[segment_and_reversal.first]);
|
||||
return out;
|
||||
}
|
||||
|
||||
ClipperLib::PolyNodes chain_clipper_polynodes(const Points &points, const ClipperLib::PolyNodes &items)
|
||||
{
|
||||
return chain_path_items(points, items);
|
||||
}
|
||||
|
||||
// BBS
|
||||
std::vector<const PrintInstance*> chain_print_object_instances(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
||||
{
|
||||
|
||||
@@ -11,11 +11,6 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace ClipperLib {
|
||||
class PolyNode;
|
||||
using PolyNodes = std::vector<PolyNode*, PointsAllocator<PolyNode*>>;
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points(const Points &points, const Point *start_near = nullptr);
|
||||
// Variant with post-processing (crossing removal + 2-opt) for object ordering.
|
||||
std::vector<size_t> chain_points_with_postprocessing(const Points &points, const Point *start_near = nullptr);
|
||||
@@ -49,7 +44,6 @@ template<typename T> inline void reorder_by_shortest_traverse(std::vector<T> &po
|
||||
for (size_t i:order) polylines_out.emplace_back(std::move(Temp[i]));
|
||||
}
|
||||
|
||||
ClipperLib::PolyNodes chain_clipper_polynodes(const Points &points, const ClipperLib::PolyNodes &items);
|
||||
|
||||
// Chain instances of print objects by an approximate shortest path.
|
||||
// Returns pairs of PrintObject idx and instance of that PrintObject.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
// #include "../ClipperZUtils.hpp"
|
||||
#include "../ClipperZUtils.hpp"
|
||||
#include "../ExtrusionEntityCollection.hpp"
|
||||
#include "../Layer.hpp"
|
||||
#include "../Print.hpp"
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "../MutablePolygon.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../Point.hpp"
|
||||
#include "clipper/clipper_z.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
@@ -39,9 +38,9 @@ namespace Slic3r {
|
||||
//FIXME this should be dependent on the nozzle diameter!
|
||||
#define SUPPORT_MATERIAL_MARGIN 1.5
|
||||
|
||||
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
|
||||
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
|
||||
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
|
||||
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtMiter, 3.
|
||||
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtMiter, 1.5
|
||||
#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtSquare, 0.
|
||||
|
||||
// Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers.
|
||||
std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interface_layers(
|
||||
@@ -280,13 +279,13 @@ SupportGeneratorLayersPtr generate_raft_base(
|
||||
polygons_append(brim, offset(ex, brim_object_gap));
|
||||
else {
|
||||
if (brim_outer)
|
||||
polygons_append(brim, offset(ex.contour, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1))));
|
||||
polygons_append(brim, offset(ex.contour, brim_object_gap, jtRound, float(scale_(0.1))));
|
||||
else
|
||||
brim.emplace_back(ex.contour);
|
||||
if (brim_inner) {
|
||||
Polygons holes = ex.holes;
|
||||
polygons_reverse(holes);
|
||||
holes = shrink(holes, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
holes = shrink(holes, brim_object_gap, jtRound, float(scale_(0.1)));
|
||||
polygons_reverse(holes);
|
||||
polygons_append(brim, std::move(holes));
|
||||
} else
|
||||
@@ -493,32 +492,25 @@ void tree_supports_generate_paths(
|
||||
// Offset expolygon inside, returns number of expolygons collected (0 or 1).
|
||||
// Vertices of output paths are marked with Z = source contour index of the expoly.
|
||||
// Vertices at the intersection of source contours are marked with Z = -1.
|
||||
auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib_Z::Paths &out) -> int
|
||||
auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, JoinType joinType, double miterLimit, ClipperZUtils::ZPaths &out) -> int
|
||||
{
|
||||
assert(delta > 0);
|
||||
auto append_paths_with_z = [](ClipperLib::Paths &src, coord_t contour_idx, ClipperLib_Z::Paths &dst) {
|
||||
auto append_paths_with_z = [](const Polygons &src, coord_t contour_idx, ClipperZUtils::ZPaths &dst) {
|
||||
dst.reserve(next_highest_power_of_2(dst.size() + src.size()));
|
||||
for (const ClipperLib::Path &contour : src) {
|
||||
ClipperLib_Z::Path tmp;
|
||||
tmp.reserve(contour.size());
|
||||
for (const Point &p : contour)
|
||||
tmp.emplace_back(p.x(), p.y(), contour_idx);
|
||||
dst.emplace_back(std::move(tmp));
|
||||
}
|
||||
for (const Polygon &contour : src)
|
||||
dst.emplace_back(ClipperZUtils::to_zpath(contour.points, contour_idx));
|
||||
};
|
||||
// Oriented CCW, the sign of d alone decides between growing and shrinking.
|
||||
auto offset_ccw = [joinType, miterLimit](Polygon polygon, float d) {
|
||||
if (! polygon.is_counter_clockwise())
|
||||
polygon.reverse();
|
||||
return offset(polygon, d, joinType, miterLimit);
|
||||
};
|
||||
|
||||
// 1) Offset the outer contour.
|
||||
ClipperLib_Z::Paths contours;
|
||||
ClipperZUtils::ZPaths contours;
|
||||
{
|
||||
ClipperLib::ClipperOffset co;
|
||||
if (joinType == jtRound)
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
co.ShortestEdgeLength = double(delta * 0.005);
|
||||
co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon);
|
||||
ClipperLib::Paths contours_raw;
|
||||
co.Execute(contours_raw, - delta);
|
||||
Polygons contours_raw = offset_ccw(expoly.contour, - delta);
|
||||
if (contours_raw.empty())
|
||||
// No need to try to offset the holes.
|
||||
return 0;
|
||||
@@ -530,24 +522,9 @@ void tree_supports_generate_paths(
|
||||
append(out, std::move(contours));
|
||||
} else {
|
||||
// 2) Offset the holes one by one, collect the offsetted holes.
|
||||
ClipperLib_Z::Paths holes;
|
||||
{
|
||||
for (const Polygon &hole : expoly.holes) {
|
||||
ClipperLib::ClipperOffset co;
|
||||
if (joinType == jtRound)
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
co.ShortestEdgeLength = double(delta * 0.005);
|
||||
co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon);
|
||||
ClipperLib::Paths out2;
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
// contours will be CCW oriented even though the input paths are CW oriented.
|
||||
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
|
||||
co.Execute(out2, delta);
|
||||
append_paths_with_z(out2, 1 + (&hole - expoly.holes.data()), holes);
|
||||
}
|
||||
}
|
||||
ClipperZUtils::ZPaths holes;
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
append_paths_with_z(offset_ccw(hole, delta), 1 + (&hole - expoly.holes.data()), holes);
|
||||
|
||||
// 3) Subtract holes from the contours.
|
||||
if (holes.empty()) {
|
||||
@@ -556,16 +533,11 @@ void tree_supports_generate_paths(
|
||||
} else {
|
||||
// Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
|
||||
// Subtract the offsetted holes from the offsetted contours.
|
||||
ClipperLib_Z::Clipper clipper;
|
||||
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) {
|
||||
//pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z()));
|
||||
ClipperZUtils::ZPaths output = ClipperZUtils::clip_zpaths(ctDifference, contours, false, holes,
|
||||
[](const ClipperZUtils::ZPoint &, const ClipperZUtils::ZPoint &, const ClipperZUtils::ZPoint &, const ClipperZUtils::ZPoint &, ClipperZUtils::ZPoint &pt) {
|
||||
// Just mark the intersection.
|
||||
pt.z() = -1;
|
||||
});
|
||||
clipper.AddPaths(contours, ClipperLib_Z::ptSubject, true);
|
||||
clipper.AddPaths(holes, ClipperLib_Z::ptClip, true);
|
||||
ClipperLib_Z::Paths output;
|
||||
clipper.Execute(ClipperLib_Z::ctDifference, output, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
if (! output.empty()) {
|
||||
append(out, std::move(output));
|
||||
} else {
|
||||
@@ -582,7 +554,7 @@ void tree_supports_generate_paths(
|
||||
// Clip the sheath path to avoid the extruder to get exactly on the first point of the loop.
|
||||
const double clip_length = spacing * 0.15;
|
||||
const double anchor_length = spacing * 6.;
|
||||
ClipperLib_Z::Paths anchor_candidates;
|
||||
ClipperZUtils::ZPaths anchor_candidates;
|
||||
for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) {
|
||||
std::unique_ptr<ExtrusionEntityCollection> eec;
|
||||
ExPolygons regions_to_draw_inner_wall{expoly};
|
||||
@@ -608,10 +580,10 @@ void tree_supports_generate_paths(
|
||||
// First genrate a 2nd perimeter loop as a source for anchor candidates.
|
||||
// The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection.
|
||||
anchor_candidates.clear();
|
||||
shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates);
|
||||
shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), jtMiter, 1.2, anchor_candidates);
|
||||
// Orient all contours CW.
|
||||
for (auto &path : anchor_candidates)
|
||||
if (ClipperLib_Z::Area(path) > 0) std::reverse(path.begin(), path.end());
|
||||
if (ClipperZUtils::area(path) > 0) std::reverse(path.begin(), path.end());
|
||||
|
||||
// Draw the perimeters.
|
||||
Polylines polylines;
|
||||
@@ -628,13 +600,13 @@ void tree_supports_generate_paths(
|
||||
pl.clip_end(clip_length);
|
||||
if (pl.size() < 2) continue;
|
||||
// Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour.
|
||||
ClipperLib_Z::Path *closest_contour = nullptr;
|
||||
Vec2d closest_point;
|
||||
int closest_point_idx = -1;
|
||||
double closest_point_t = 0.;
|
||||
double d2min = std::numeric_limits<double>::max();
|
||||
Vec2d seam_pt = pl.back().cast<double>();
|
||||
for (ClipperLib_Z::Path &path : anchor_candidates)
|
||||
ClipperZUtils::ZPath *closest_contour = nullptr;
|
||||
Vec2d closest_point;
|
||||
int closest_point_idx = -1;
|
||||
double closest_point_t = 0.;
|
||||
double d2min = std::numeric_limits<double>::max();
|
||||
Vec2d seam_pt = pl.back().cast<double>();
|
||||
for (ClipperZUtils::ZPath &path : anchor_candidates)
|
||||
for (int i = 0; i < int(path.size()); ++i) {
|
||||
int j = next_idx_modulo(i, path);
|
||||
if (path[i].z() == idx_loop || path[j].z() == idx_loop) {
|
||||
@@ -662,14 +634,14 @@ void tree_supports_generate_paths(
|
||||
// Try to cut an anchor from the closest_contour.
|
||||
// Both closest_contour and pl are CW oriented.
|
||||
pl.points.emplace_back(closest_point.cast<coord_t>());
|
||||
const ClipperLib_Z::Path &path = *closest_contour;
|
||||
double remaining_length = anchor_length - (seam_pt - closest_point).norm();
|
||||
int i = closest_point_idx;
|
||||
int j = next_idx_modulo(i, *closest_contour);
|
||||
Vec2d pi(path[i].x(), path[i].y());
|
||||
Vec2d pj(path[j].x(), path[j].y());
|
||||
Vec2d v = pj - pi;
|
||||
double l = v.norm();
|
||||
const ClipperZUtils::ZPath &path = *closest_contour;
|
||||
double remaining_length = anchor_length - (seam_pt - closest_point).norm();
|
||||
int i = closest_point_idx;
|
||||
int j = next_idx_modulo(i, *closest_contour);
|
||||
Vec2d pi(path[i].x(), path[i].y());
|
||||
Vec2d pj(path[j].x(), path[j].y());
|
||||
Vec2d v = pj - pi;
|
||||
double l = v.norm();
|
||||
if (remaining_length < (1. - closest_point_t) * l) {
|
||||
// Just trim the current line.
|
||||
pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast<coord_t>());
|
||||
|
||||
@@ -63,9 +63,9 @@ namespace Slic3r {
|
||||
#define PILLAR_SIZE (2.5)
|
||||
#define PILLAR_SPACING 10
|
||||
|
||||
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
|
||||
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
|
||||
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
|
||||
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtMiter, 3.
|
||||
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtMiter, 1.5
|
||||
#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtSquare, 0.
|
||||
|
||||
static constexpr bool support_with_sheath = false;
|
||||
|
||||
@@ -1634,7 +1634,7 @@ static inline std::tuple<Polygons, Polygons, double> detect_contacts(
|
||||
offset(
|
||||
diff_polygons,
|
||||
scaled<float>(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS),
|
||||
ClipperLib::jtRound,
|
||||
jtRound,
|
||||
// round mitter limit
|
||||
scale_(0.05)),
|
||||
slices_margin.polygons);
|
||||
|
||||
@@ -112,7 +112,7 @@ TreeModelVolumes::TreeModelVolumes(
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(num_raft_layers, num_layers, std::min<size_t>(1, std::max<size_t>(16, num_layers / (8 * tbb::this_task_arena::max_concurrency())))),
|
||||
[&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
|
||||
outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution, polygons_strictly_simple);
|
||||
outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
@@ -462,7 +462,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
// if a key does not exist when it is accessed it is added!
|
||||
collision_areas_offsetted[layer_idx] = offset_value == 0 ?
|
||||
union_(collision_areas) :
|
||||
offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2);
|
||||
offset(union_ex(collision_areas), offset_value, jtMiter, 1.2);
|
||||
if(throw_on_cancel)
|
||||
throw_on_cancel();
|
||||
}
|
||||
@@ -515,16 +515,16 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
// the conditional -0.5 ensures that plastic can never touch on the diagonal
|
||||
// downward when the z_distance_top_layers = 1. It is assumed to be better to
|
||||
// not support an overhang<90 degree than to risk fusing to it.
|
||||
append(collisions, offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2));
|
||||
append(collisions, offset(union_ex(collision_areas_original), radius + required_range_x, jtMiter, 1.2));
|
||||
}
|
||||
collisions = processing_last_mesh && layer_idx < int(anti_overhang.size()) ?
|
||||
union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) :
|
||||
union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, jtMiter, 1.2)) :
|
||||
union_(collisions);
|
||||
auto &dst = data[layer_idx];
|
||||
if (processing_last_mesh) {
|
||||
if (! dst.empty())
|
||||
collisions = union_(collisions, dst);
|
||||
dst = polygons_simplify(collisions, min_resolution, polygons_strictly_simple);
|
||||
dst = polygons_simplify(collisions, min_resolution);
|
||||
} else
|
||||
append(dst, std::move(collisions));
|
||||
if (throw_on_cancel)
|
||||
@@ -552,7 +552,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
if (processing_last_mesh) {
|
||||
if (! dst.empty())
|
||||
placable = union_(placable, dst);
|
||||
dst = polygons_simplify(placable, min_resolution, polygons_strictly_simple);
|
||||
dst = polygons_simplify(placable, min_resolution);
|
||||
} else
|
||||
append(dst, placable);
|
||||
if (throw_on_cancel)
|
||||
@@ -601,8 +601,8 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::vector<RadiusLayerP
|
||||
// this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area.
|
||||
data.emplace_back(RadiusLayerPair(radius, layer_idx), polygons_simplify(
|
||||
offset(union_ex(this->getCollision(m_increase_until_radius, layer_idx, false)),
|
||||
5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution),
|
||||
m_min_resolution, polygons_strictly_simple));
|
||||
5 - increase_radius_ceil, jtRound, m_min_resolution),
|
||||
m_min_resolution));
|
||||
if (throw_on_cancel)
|
||||
throw_on_cancel();
|
||||
}
|
||||
@@ -689,10 +689,10 @@ void TreeModelVolumes::calculateAvoidance(const std::vector<RadiusLayerPair> &ke
|
||||
latest_avoidance = union_(current_layer_collisions,
|
||||
offset(latest_avoidance,
|
||||
istep + 1 == move_steps ? - last_move_step : - move_step,
|
||||
ClipperLib::jtRound, m_min_resolution));
|
||||
jtRound, m_min_resolution));
|
||||
if (task.to_model)
|
||||
latest_avoidance = diff(latest_avoidance, getPlaceableAreas(task.radius, layer_idx, throw_on_cancel));
|
||||
latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution, polygons_strictly_simple);
|
||||
latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution);
|
||||
data.emplace_back(RadiusLayerPair{task.radius, layer_idx}, latest_avoidance);
|
||||
if (throw_on_cancel)
|
||||
throw_on_cancel();
|
||||
@@ -815,12 +815,12 @@ void TreeModelVolumes::calculateWallRestrictions(const std::vector<RadiusLayerPa
|
||||
data[layer_idx - min_layer_bottom] = polygons_simplify(
|
||||
// radius contains m_current_min_xy_dist_delta already if required
|
||||
intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx - 1, true)),
|
||||
m_min_resolution, polygons_strictly_simple);
|
||||
m_min_resolution);
|
||||
if (! data_min.empty())
|
||||
data_min[layer_idx - min_layer_bottom] =
|
||||
polygons_simplify(
|
||||
intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx - 1, true)),
|
||||
m_min_resolution, polygons_strictly_simple);
|
||||
m_min_resolution);
|
||||
if (throw_on_cancel)
|
||||
throw_on_cancel();
|
||||
}
|
||||
|
||||
@@ -664,7 +664,7 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p
|
||||
}
|
||||
|
||||
|
||||
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
|
||||
#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtSquare, 0.
|
||||
void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/)
|
||||
{
|
||||
bool tree_support_enable = m_object_config->enable_support.value && is_tree(m_object_config->support_type.value);
|
||||
@@ -2571,7 +2571,7 @@ void TreeSupport::draw_circles()
|
||||
Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2);
|
||||
hole_lower.translate(direction);
|
||||
// note to expand a hole, we need to do negative offset
|
||||
auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, ClipperLib::JoinType::jtSquare);
|
||||
auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, jtSquare);
|
||||
if (!hole_expanded.empty()) {
|
||||
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
|
||||
holePropagationInfos.insert({ &base_area_lower.holes.back(), {25, direction, pt_far_on_poly} });
|
||||
@@ -2584,7 +2584,7 @@ void TreeSupport::draw_circles()
|
||||
auto&& direction = std::get<1>(holePropagationInfos[&hole]);
|
||||
hole_lower.translate(direction);
|
||||
// note to shrink a hole, we need to do positive offset
|
||||
auto hole_expanded = offset(hole_lower, line_width_scaled / 2, ClipperLib::JoinType::jtSquare);
|
||||
auto hole_expanded = offset(hole_lower, line_width_scaled / 2, jtSquare);
|
||||
Point farPoint = std::get<2>(holePropagationInfos[&hole]) + direction * 2;
|
||||
if (!hole_expanded.empty()) {
|
||||
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
|
||||
|
||||
@@ -68,7 +68,7 @@ static inline void validate_range(const Point &pt)
|
||||
{
|
||||
static constexpr const int32_t hi = 65536 * 16384;
|
||||
if (pt.x() > hi || pt.y() > hi || -pt.x() > hi || -pt.y() > hi)
|
||||
throw ClipperLib::clipperException("Coordinate outside allowed range");
|
||||
throw RuntimeError("Coordinate outside allowed range");
|
||||
}
|
||||
|
||||
static inline void validate_range(const Points &points)
|
||||
@@ -832,16 +832,16 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
|
||||
}
|
||||
// offset in steps
|
||||
for (int i = 0; i < steps; ++ i) {
|
||||
ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled<float>(0.01)), collision_trimmed());
|
||||
ret = diff(offset(ret, step_size, jtRound, scaled<float>(0.01)), collision_trimmed());
|
||||
// ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound.
|
||||
if (i % 10 == 7)
|
||||
ret = polygons_simplify(ret, scaled<double>(0.015), polygons_strictly_simple);
|
||||
ret = polygons_simplify(ret, scaled<double>(0.015));
|
||||
}
|
||||
// offset the remainder
|
||||
float last_offset = distance - steps * step_size;
|
||||
if (last_offset > SCALED_EPSILON)
|
||||
ret = offset(ret, distance - steps * step_size, ClipperLib::jtRound, scaled<float>(0.01));
|
||||
ret = polygons_simplify(ret, scaled<double>(0.015), polygons_strictly_simple);
|
||||
ret = offset(ret, distance - steps * step_size, jtRound, scaled<float>(0.01));
|
||||
ret = polygons_simplify(ret, scaled<double>(0.015));
|
||||
|
||||
if (do_final_difference)
|
||||
ret = diff(ret, collision_trimmed());
|
||||
@@ -1622,8 +1622,8 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di
|
||||
safe_movement_distance, safe_movement_distance + radius, 1);
|
||||
}
|
||||
if (settings.no_error && settings.move)
|
||||
// as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance.
|
||||
polygons_simplify(increased, scaled<float>(0.025), polygons_strictly_simple);
|
||||
// as jtRound has to be used for offsets this simplify is VERY important for performance.
|
||||
polygons_simplify(increased, scaled<float>(0.025));
|
||||
} else
|
||||
// if no movement is done the areas keep parent area as no move == offset(0)
|
||||
increased = parent.influence_area;
|
||||
@@ -4145,7 +4145,7 @@ void organic_draw_branches(
|
||||
base_layer_polygons = smooth_outward(union_(base_layer_polygons), config.support_line_width); //FIXME was .smooth(50);
|
||||
//smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) :
|
||||
// simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution.
|
||||
base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled<double>(0.03), double(config.resolution)), polygons_strictly_simple);
|
||||
base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled<double>(0.03), double(config.resolution)));
|
||||
}
|
||||
|
||||
// Subtract top contact layer polygons from support base.
|
||||
|
||||
@@ -588,8 +588,6 @@ private:
|
||||
// std::vector<coord_t> known_z;
|
||||
};
|
||||
|
||||
static constexpr const bool polygons_strictly_simple = false;
|
||||
|
||||
inline double tiny_area_threshold() { return sqr(scaled<double>(0.001)); }
|
||||
|
||||
inline void tree_supports_show_error(std::string_view message, bool critical)
|
||||
|
||||
@@ -1856,7 +1856,7 @@ static ExPolygons make_expolygons_simple(std::vector<IntersectionLine> &lines)
|
||||
return slices;
|
||||
}
|
||||
|
||||
static void make_expolygons(const Polygons &loops, const float closing_radius, const float extra_offset, ClipperLib::PolyFillType fill_type, ExPolygons* slices)
|
||||
static void make_expolygons(const Polygons &loops, const float closing_radius, const float extra_offset, PolyFillType fill_type, ExPolygons* slices)
|
||||
{
|
||||
/*
|
||||
Input loops are not suitable for evenodd nor nonzero fill types, as we might get
|
||||
@@ -2156,8 +2156,8 @@ std::vector<ExPolygons> slice_mesh_ex(
|
||||
const auto this_mode = layer_id < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode;
|
||||
Slic3r::make_expolygons(
|
||||
layers_p[layer_id], params.closing_radius, params.extra_offset,
|
||||
this_mode == MeshSlicingParams::SlicingMode::EvenOdd ? ClipperLib::pftEvenOdd :
|
||||
this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour ? ClipperLib::pftPositive : ClipperLib::pftNonZero,
|
||||
this_mode == MeshSlicingParams::SlicingMode::EvenOdd ? pftEvenOdd :
|
||||
this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour ? pftPositive : pftNonZero,
|
||||
&expolygons);
|
||||
//FIXME simplify
|
||||
if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour)
|
||||
|
||||
@@ -12,13 +12,13 @@ struct MeshSlicingParams
|
||||
{
|
||||
enum class SlicingMode : uint32_t {
|
||||
// Regular slicing, maintain all contours and their orientation.
|
||||
// slice_mesh_ex() applies ClipperLib::pftNonZero rule to the result of slice_mesh().
|
||||
// slice_mesh_ex() applies pftNonZero rule to the result of slice_mesh().
|
||||
Regular,
|
||||
// For slicing 3DLabPrints plane models (aka to be compatible with S3D default strategy).
|
||||
// slice_mesh_ex() applies ClipperLib::pftEvenOdd rule. slice_mesh() slices EvenOdd as Regular.
|
||||
// slice_mesh_ex() applies pftEvenOdd rule. slice_mesh() slices EvenOdd as Regular.
|
||||
EvenOdd,
|
||||
// Maintain all contours, orient all contours CCW.
|
||||
// slice_mesh_ex() applies ClipperLib::pftNonZero rule, thus holes will be closed.
|
||||
// slice_mesh_ex() applies pftNonZero rule, thus holes will be closed.
|
||||
Positive,
|
||||
// Orient all contours CCW and keep only the contour with the largest area.
|
||||
// This mode is useful for slicing complex objects in vase mode.
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// Hackish wrapper around the ClipperLib library to compile the Clipper library using Slic3r::Point.
|
||||
|
||||
#include "clipper.hpp"
|
||||
|
||||
// Don't include <clipper/clipper.hpp> for the second time.
|
||||
#define clipper_hpp
|
||||
|
||||
// Override ClipperLib namespace to Slic3r::ClipperLib
|
||||
#define CLIPPERLIB_NAMESPACE_PREFIX Slic3r
|
||||
// Override Slic3r::ClipperLib::IntPoint to Slic3r::Point
|
||||
#define CLIPPERLIB_INTPOINT_TYPE Slic3r::Point
|
||||
|
||||
#include <clipper/clipper.cpp>
|
||||
@@ -1,26 +0,0 @@
|
||||
// Hackish wrapper around the ClipperLib library to compile the Clipper library using Slic3r's own Point type.
|
||||
|
||||
#ifndef slic3r_clipper_hpp
|
||||
|
||||
#ifdef clipper_hpp
|
||||
#error "You should include the libslic3r/clipper.hpp before clipper/clipper.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef CLIPPERLIB_USE_XYZ
|
||||
#error "Something went wrong. Using clipper.hpp with Slic3r Point type, but CLIPPERLIB_USE_XYZ is defined."
|
||||
#endif
|
||||
|
||||
#define slic3r_clipper_hpp
|
||||
|
||||
#include "Point.hpp"
|
||||
|
||||
#define CLIPPERLIB_NAMESPACE_PREFIX Slic3r
|
||||
#define CLIPPERLIB_INTPOINT_TYPE Slic3r::Point
|
||||
|
||||
#include <clipper/clipper.hpp>
|
||||
|
||||
#undef clipper_hpp
|
||||
#undef CLIPPERLIB_NAMESPACE_PREFIX
|
||||
#undef CLIPPERLIB_INTPOINT_TYPE
|
||||
|
||||
#endif // slic3r_clipper_hpp
|
||||
@@ -110,8 +110,6 @@
|
||||
#include <cereal/access.hpp>
|
||||
#include <cereal/types/base_class.hpp>
|
||||
|
||||
#include <clipper/clipper_z.hpp>
|
||||
#include "clipper.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Config.hpp"
|
||||
|
||||
@@ -37,6 +37,8 @@ static constexpr float MAX_WIPES_RADIUS_MM = 1.0f;
|
||||
// Used for positions, displacements and so on.
|
||||
//
|
||||
using Vec3 = std::array<float, 3>;
|
||||
// ORCA: the per vertex data uploaded as GL_RGBA32F, see extract_pos_and_or_hwa().
|
||||
using Vec4 = std::array<float, 4>;
|
||||
|
||||
//
|
||||
// 4x4 square matrix with elements in column-major order:
|
||||
|
||||
@@ -76,6 +76,14 @@ public:
|
||||
// caller decides which of the two it varies with the realistic view setting.
|
||||
//
|
||||
void set_tone(float exposure, float saturation);
|
||||
//
|
||||
// ORCA: section view. Cuts away where dot(plane, (x, y, z, 1)) < 0; { 0, 0, 0, 1 } keeps all.
|
||||
//
|
||||
void set_clipping_plane(const std::array<float, 4>& plane);
|
||||
//
|
||||
// ORCA: direction to the top light in eye space, shading the toolpaths and biasing their shadow lookup.
|
||||
//
|
||||
void set_light_top_dir(const Vec3& direction);
|
||||
|
||||
//
|
||||
// ************************************************************************
|
||||
|
||||
@@ -14,7 +14,7 @@ static const char* Segments_Vertex_Shader =
|
||||
"#version 150\n"
|
||||
"#define POINTY_CAPS\n"
|
||||
"#define FIX_TWISTING\n"
|
||||
"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
|
||||
"uniform vec3 light_top_dir;\n"
|
||||
"const float light_top_diffuse = 0.6 * 0.8;\n"
|
||||
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
|
||||
"const float light_top_specular = 0.6 * 0.25;\n"
|
||||
@@ -31,13 +31,11 @@ static const char* Segments_Vertex_Shader =
|
||||
"uniform samplerBuffer height_width_angle_tex;\n"
|
||||
"uniform samplerBuffer color_tex;\n"
|
||||
"uniform usamplerBuffer segment_index_tex;\n"
|
||||
// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not
|
||||
// world_position, so the caster would write a depth the receiver never looks up.
|
||||
"uniform float bias_scale;\n"
|
||||
// draw the instances last to first, top layers before the ones they hide, so that early depth
|
||||
// rejection discards most of the hidden fragments; set when the camera looks down on the print
|
||||
"uniform int reverse_order;\n"
|
||||
"uniform int instance_count;\n"
|
||||
// ORCA: draw last segment first, so looking down the top layers hide the rest from the fragment shader.
|
||||
"uniform bool reverse_order;\n"
|
||||
"uniform int instances_count;\n"
|
||||
// ORCA: section view
|
||||
"uniform vec4 clipping_plane;\n"
|
||||
"in int vertex_id;\n"
|
||||
"out vec3 color;\n"
|
||||
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
|
||||
@@ -48,13 +46,25 @@ static const char* Segments_Vertex_Shader =
|
||||
"// needs its world position and, for the depth bias, its eye space normal.\n"
|
||||
"out vec3 world_position;\n"
|
||||
"out vec3 shadow_normal;\n"
|
||||
"vec3 decode_color(float color) {\n"
|
||||
// ORCA: section view - the segment and the light on its cut face
|
||||
"flat out int segment_id;\n"
|
||||
"out vec3 cut_color_direct;\n"
|
||||
// ORCA: the layers greyed or dimmed around the one the sliders show, see ViewerImpl::set_layer_colors().
|
||||
"uniform ivec2 lit_layers;\n"
|
||||
"uniform int grey_below_layer;\n"
|
||||
"uniform float dim_brightness;\n"
|
||||
"uniform int kept_vertex;\n"
|
||||
"vec3 decode_color(float color, int id, int layer) {\n"
|
||||
" int c = int(round(color));\n"
|
||||
" int r = (c >> 16) & 0xFF;\n"
|
||||
" int g = (c >> 8) & 0xFF;\n"
|
||||
" int b = (c >> 0) & 0xFF;\n"
|
||||
" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
|
||||
" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
|
||||
" if (dim_brightness >= 0.0)\n"
|
||||
" rgb = floor(rgb * dim_brightness);\n"
|
||||
" else if (layer < grey_below_layer)\n"
|
||||
" rgb = vec3(64.0);\n"
|
||||
" }\n"
|
||||
" float f = 1.0 / 255.0f;\n"
|
||||
" return f * vec3(r, g, b);\n"
|
||||
" return f * rgb;\n"
|
||||
"}\n"
|
||||
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
|
||||
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
|
||||
@@ -63,7 +73,7 @@ static const char* Segments_Vertex_Shader =
|
||||
" return top_diffuse + front_diffuse + top_specular;\n"
|
||||
"}\n"
|
||||
"void main() {\n"
|
||||
" int instance = (reverse_order != 0) ? instance_count - 1 - gl_InstanceID : gl_InstanceID;\n"
|
||||
" int instance = reverse_order ? instances_count - 1 - gl_InstanceID : gl_InstanceID;\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
|
||||
" int id_b = id_a + 1;\n"
|
||||
" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
|
||||
@@ -152,22 +162,73 @@ static const char* Segments_Vertex_Shader =
|
||||
" }\n"
|
||||
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
|
||||
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
|
||||
" eye_position.z += bias * bias_scale;\n"
|
||||
" eye_position.z += bias;\n"
|
||||
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
|
||||
" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
|
||||
" vec3 color_base = decode_color(texelFetch(color_tex, id).r, id, int(texelFetch(position_tex, id).w));\n"
|
||||
" color = color_base * (ambient + emission);\n"
|
||||
" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n"
|
||||
" world_position = pos;\n"
|
||||
" shadow_normal = eye_normal;\n"
|
||||
" segment_id = id_a;\n"
|
||||
" cut_color_direct = clipping_plane.xyz == vec3(0.0) ? vec3(0.0) :\n"
|
||||
" color_base * direct_lighting(eye_position, mat3(view_matrix) * -clipping_plane.xyz);\n"
|
||||
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
// ORCA: realistic view - shadow caster, one light facing ribbon per segment, not instanced as tiny instances are slow.
|
||||
static const char* Segments_Shadow_Caster_Vertex_Shader =
|
||||
"#version 150\n"
|
||||
"const vec3 UP = vec3(0, 0, 1);\n"
|
||||
"const int CORNERS[6] = int[](0, 1, 2, 0, 2, 3);\n"
|
||||
"uniform mat4 view_matrix;\n"
|
||||
"uniform mat4 projection_matrix;\n"
|
||||
"uniform samplerBuffer position_tex;\n"
|
||||
"uniform samplerBuffer height_width_angle_tex;\n"
|
||||
"uniform usamplerBuffer segment_index_tex;\n"
|
||||
"uniform bool reverse_order;\n"
|
||||
"uniform int instances_count;\n"
|
||||
"out vec3 world_position;\n"
|
||||
"void main() {\n"
|
||||
" int segment = gl_VertexID / 6;\n"
|
||||
" int corner = CORNERS[gl_VertexID - 6 * segment];\n"
|
||||
" int instance = reverse_order ? instances_count - 1 - segment : segment;\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
|
||||
" int id = corner < 2 ? id_a : id_a + 1;\n"
|
||||
" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
|
||||
" vec3 pos_b = texelFetch(position_tex, id_a + 1).xyz;\n"
|
||||
" vec3 line = pos_b - pos_a;\n"
|
||||
" float line_len = length(line);\n"
|
||||
" vec3 line_dir = line_len < 1e-4 ? vec3(1.0, 0.0, 0.0) : line / line_len;\n"
|
||||
" vec3 line_right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n"
|
||||
" vec3 line_up_dir = normalize(cross(line_right_dir, line_dir));\n"
|
||||
" vec3 to_light = vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]);\n"
|
||||
" vec3 side_dir = cross(to_light, line_dir);\n"
|
||||
" side_dir = dot(side_dir, side_dir) < 1e-8 ? line_right_dir : normalize(side_dir);\n"
|
||||
" vec2 height_width = texelFetch(height_width_angle_tex, id).xy;\n"
|
||||
" float half_extent = 0.5 * (height_width.y * abs(dot(line_right_dir, side_dir)) + height_width.x * abs(dot(line_up_dir, side_dir)));\n"
|
||||
" float along = (corner < 2 ? -0.5 : 0.5) * height_width.y;\n"
|
||||
" float side = (corner == 0 || corner == 3) ? -1.0 : 1.0;\n"
|
||||
" vec3 pos = (corner < 2 ? pos_a : pos_b) + along * line_dir + side * half_extent * side_dir;\n"
|
||||
" world_position = pos;\n"
|
||||
" gl_Position = projection_matrix * (view_matrix * vec4(pos, 1.0));\n"
|
||||
"}\n";
|
||||
|
||||
// ORCA: realistic view - the shadow caster writes depth only. What a section cuts away casts no shadow.
|
||||
static const char* Segments_Shadow_Caster_Fragment_Shader =
|
||||
"#version 150\n"
|
||||
"uniform vec4 clipping_plane;\n"
|
||||
"in vec3 world_position;\n"
|
||||
"void main() {\n"
|
||||
" if (dot(vec4(world_position, 1.0), clipping_plane) < 0.0)\n"
|
||||
" discard;\n"
|
||||
"}\n";
|
||||
|
||||
static const char* Segments_Fragment_Shader =
|
||||
"#version 150\n"
|
||||
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
|
||||
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
|
||||
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
|
||||
"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
|
||||
"uniform vec3 light_top_dir;\n"
|
||||
"uniform sampler2D shadow_map;\n"
|
||||
"uniform mat4 shadow_light_vp;\n"
|
||||
"uniform float shadow_intensity;\n"
|
||||
@@ -178,23 +239,33 @@ static const char* Segments_Fragment_Shader =
|
||||
// passes in each mode is decided in GLCanvas3D::_render_gcode, not here.
|
||||
"uniform float exposure;\n"
|
||||
"uniform float saturation;\n"
|
||||
// ORCA: section view - only camera-facing faces are drawn, so cut faces are ray cast
|
||||
"uniform vec4 clipping_plane;\n"
|
||||
"uniform mat4 view_matrix;\n"
|
||||
"uniform mat4 projection_matrix;\n"
|
||||
"uniform vec3 camera_position;\n"
|
||||
"uniform samplerBuffer position_tex;\n"
|
||||
"uniform samplerBuffer height_width_angle_tex;\n"
|
||||
"const vec3 UP = vec3(0, 0, 1);\n"
|
||||
"const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n"
|
||||
"in vec3 color;\n"
|
||||
"in vec3 color_direct;\n"
|
||||
"in vec3 world_position;\n"
|
||||
"in vec3 shadow_normal;\n"
|
||||
"flat in int segment_id;\n"
|
||||
"in vec3 cut_color_direct;\n"
|
||||
"out vec4 fragment_color;\n"
|
||||
"float shadow_shade() {\n"
|
||||
"float shadow_shade(vec3 position, vec3 normal) {\n"
|
||||
" if (shadow_intensity <= 0.0)\n"
|
||||
" return 1.0;\n"
|
||||
" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n"
|
||||
" vec4 lp = shadow_light_vp * vec4(position, 1.0);\n"
|
||||
" vec3 proj = lp.xyz / lp.w;\n"
|
||||
" proj = proj * 0.5 + 0.5;\n"
|
||||
" if (proj.z > 1.0)\n"
|
||||
" return 1.0;\n"
|
||||
" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n"
|
||||
" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n"
|
||||
" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n"
|
||||
" float NdotL = dot(normalize(normal), light_top_dir);\n"
|
||||
" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n"
|
||||
" float sum = 0.0;\n"
|
||||
" for (int x = -2; x <= 2; ++x) {\n"
|
||||
@@ -205,8 +276,39 @@ static const char* Segments_Fragment_Shader =
|
||||
" }\n"
|
||||
" return 1.0 - shadow_intensity * (sum / 25.0);\n"
|
||||
"}\n"
|
||||
// ORCA: section view - where the view ray meets the plane, if inside the extrusion's diamond section
|
||||
"bool find_cut(out vec3 cut) {\n"
|
||||
// parallel rays for an orthographic projection
|
||||
" vec3 ray = projection_matrix[3][3] == 1.0 ? -vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]) :\n"
|
||||
" normalize(world_position - camera_position);\n"
|
||||
" float approach = dot(clipping_plane.xyz, ray);\n"
|
||||
" if (approach <= 0.0)\n"
|
||||
" return false;\n"
|
||||
" cut = world_position - dot(vec4(world_position, 1.0), clipping_plane) / approach * ray;\n"
|
||||
" vec3 pos_a = texelFetch(position_tex, segment_id).xyz;\n"
|
||||
" vec3 line = texelFetch(position_tex, segment_id + 1).xyz - pos_a;\n"
|
||||
" float length_sq = dot(line, line);\n"
|
||||
" float along = length_sq < 1e-8 ? -1.0 : dot(cut - pos_a, line) / length_sq;\n"
|
||||
" if (along < 0.0 || along > 1.0)\n"
|
||||
" return false;\n"
|
||||
" vec3 line_dir = line * inversesqrt(length_sq);\n"
|
||||
" vec3 right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n"
|
||||
" vec3 offset = cut - pos_a - along * line;\n"
|
||||
" vec2 half_height_width = 0.5 * mix(texelFetch(height_width_angle_tex, segment_id).xy,\n"
|
||||
" texelFetch(height_width_angle_tex, segment_id + 1).xy, along);\n"
|
||||
" return abs(dot(offset, right_dir)) / half_height_width.y + abs(dot(offset, cross(right_dir, line_dir))) / half_height_width.x <= 1.0;\n"
|
||||
"}\n"
|
||||
"void main() {\n"
|
||||
" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n"
|
||||
" vec3 c;\n"
|
||||
" if (dot(vec4(world_position, 1.0), clipping_plane) >= 0.0)\n"
|
||||
" c = color + color_direct * shadow_shade(world_position, shadow_normal);\n"
|
||||
" else {\n"
|
||||
" vec3 cut;\n"
|
||||
" if (!find_cut(cut))\n"
|
||||
" discard;\n"
|
||||
" c = color + cut_color_direct * shadow_shade(cut, mat3(view_matrix) * -clipping_plane.xyz);\n"
|
||||
" }\n"
|
||||
" c *= exposure;\n"
|
||||
" c = mix(vec3(dot(c, LUMA)), c, saturation);\n"
|
||||
" fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n"
|
||||
"}\n";
|
||||
@@ -234,16 +336,28 @@ static const char* Options_Vertex_Shader =
|
||||
"uniform samplerBuffer height_width_angle_tex;\n"
|
||||
"uniform samplerBuffer color_tex;\n"
|
||||
"uniform usamplerBuffer segment_index_tex;\n"
|
||||
// ORCA: section view
|
||||
"uniform vec4 clipping_plane;\n"
|
||||
"in vec3 in_position;\n"
|
||||
"in vec3 in_normal;\n"
|
||||
"out vec3 color;\n"
|
||||
"vec3 decode_color(float color) {\n"
|
||||
"out float clipping_plane_dot;\n"
|
||||
// ORCA: as in Segments_Vertex_Shader.
|
||||
"uniform ivec2 lit_layers;\n"
|
||||
"uniform int grey_below_layer;\n"
|
||||
"uniform float dim_brightness;\n"
|
||||
"uniform int kept_vertex;\n"
|
||||
"vec3 decode_color(float color, int id, int layer) {\n"
|
||||
" int c = int(round(color));\n"
|
||||
" int r = (c >> 16) & 0xFF;\n"
|
||||
" int g = (c >> 8) & 0xFF;\n"
|
||||
" int b = (c >> 0) & 0xFF;\n"
|
||||
" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
|
||||
" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
|
||||
" if (dim_brightness >= 0.0)\n"
|
||||
" rgb = floor(rgb * dim_brightness);\n"
|
||||
" else if (layer < grey_below_layer)\n"
|
||||
" rgb = vec3(64.0);\n"
|
||||
" }\n"
|
||||
" float f = 1.0 / 255.0f;\n"
|
||||
" return f * vec3(r, g, b);\n"
|
||||
" return f * rgb;\n"
|
||||
"}\n"
|
||||
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
|
||||
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
|
||||
@@ -263,11 +377,13 @@ static const char* Options_Vertex_Shader =
|
||||
" height_width.y, 0.0, 0.0,\n"
|
||||
" 0.0, height_width.y, 0.0,\n"
|
||||
" 0.0, 0.0, height_width.x);\n"
|
||||
// ORCA: section view - markers are cut away whole, by their centre
|
||||
" clipping_plane_dot = dot(vec4(offset, 1.0), clipping_plane);\n"
|
||||
" vec3 eye_position = (view_matrix * vec4(scale_matrix * in_position + offset, 1.0)).xyz;\n"
|
||||
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
|
||||
" eye_position.z += bias;\n"
|
||||
" vec3 eye_normal = (view_matrix * vec4(in_normal, 0.0)).xyz;\n"
|
||||
" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
|
||||
" vec3 color_base = decode_color(texelFetch(color_tex, id).r, id, int(texelFetch(position_tex, id).w));\n"
|
||||
" color = color_base * lighting(eye_position, eye_normal);\n"
|
||||
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
|
||||
"}\n";
|
||||
@@ -275,8 +391,11 @@ static const char* Options_Vertex_Shader =
|
||||
static const char* Options_Fragment_Shader =
|
||||
"#version 150\n"
|
||||
"in vec3 color;\n"
|
||||
"in float clipping_plane_dot;\n"
|
||||
"out vec4 fragment_color;\n"
|
||||
"void main() {\n"
|
||||
" if (clipping_plane_dot < 0.0)\n"
|
||||
" discard;\n"
|
||||
" fragment_color = vec4(color, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ static const char* Segments_Vertex_Shader_ES =
|
||||
"precision lowp usampler2D;\n"
|
||||
"#define POINTY_CAPS\n"
|
||||
"#define FIX_TWISTING\n"
|
||||
"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
|
||||
"uniform vec3 light_top_dir;\n"
|
||||
"const float light_top_diffuse = 0.6 * 0.8;\n"
|
||||
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
|
||||
"const float light_top_specular = 0.6 * 0.25;\n"
|
||||
@@ -32,6 +32,11 @@ static const char* Segments_Vertex_Shader_ES =
|
||||
"uniform sampler2D height_width_angle_tex;\n"
|
||||
"uniform sampler2D color_tex;\n"
|
||||
"uniform usampler2D segment_index_tex;\n"
|
||||
// ORCA: draw last segment first, so looking down the top layers hide the rest from the fragment shader.
|
||||
"uniform bool reverse_order;\n"
|
||||
"uniform int instances_count;\n"
|
||||
// ORCA: section view
|
||||
"uniform vec4 clipping_plane;\n"
|
||||
"in float vertex_id_float;\n"
|
||||
"out vec3 color;\n"
|
||||
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
|
||||
@@ -42,13 +47,25 @@ static const char* Segments_Vertex_Shader_ES =
|
||||
"// needs its world position and, for the depth bias, its eye space normal.\n"
|
||||
"out vec3 world_position;\n"
|
||||
"out vec3 shadow_normal;\n"
|
||||
"vec3 decode_color(float color) {\n"
|
||||
// ORCA: section view - the segment and the light on its cut face
|
||||
"flat out int segment_id;\n"
|
||||
"out vec3 cut_color_direct;\n"
|
||||
// ORCA: the layers greyed or dimmed around the one the sliders show, see ViewerImpl::set_layer_colors().
|
||||
"uniform ivec2 lit_layers;\n"
|
||||
"uniform int grey_below_layer;\n"
|
||||
"uniform float dim_brightness;\n"
|
||||
"uniform int kept_vertex;\n"
|
||||
"vec3 decode_color(float color, int id, int layer) {\n"
|
||||
" int c = int(round(color));\n"
|
||||
" int r = (c >> 16) & 0xFF;\n"
|
||||
" int g = (c >> 8) & 0xFF;\n"
|
||||
" int b = (c >> 0) & 0xFF;\n"
|
||||
" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
|
||||
" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
|
||||
" if (dim_brightness >= 0.0)\n"
|
||||
" rgb = floor(rgb * dim_brightness);\n"
|
||||
" else if (layer < grey_below_layer)\n"
|
||||
" rgb = vec3(64.0);\n"
|
||||
" }\n"
|
||||
" float f = 1.0 / 255.0f;\n"
|
||||
" return f * vec3(r, g, b);\n"
|
||||
" return f * rgb;\n"
|
||||
"}\n"
|
||||
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
|
||||
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
|
||||
@@ -66,7 +83,8 @@ static const char* Segments_Vertex_Shader_ES =
|
||||
"}\n"
|
||||
"void main() {\n"
|
||||
" int vertex_id = int(vertex_id_float);\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, tex_coord_u(segment_index_tex, gl_InstanceID), 0).r);\n"
|
||||
" int instance = reverse_order ? instances_count - 1 - gl_InstanceID : gl_InstanceID;\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, tex_coord_u(segment_index_tex, instance), 0).r);\n"
|
||||
" int id_b = id_a + 1;\n"
|
||||
" vec3 pos_a = texelFetch(position_tex, tex_coord(position_tex, id_a), 0).xyz;\n"
|
||||
" vec3 pos_b = texelFetch(position_tex, tex_coord(position_tex, id_b), 0).xyz;\n"
|
||||
@@ -151,24 +169,87 @@ static const char* Segments_Vertex_Shader_ES =
|
||||
" }\n"
|
||||
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
|
||||
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
|
||||
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r);\n"
|
||||
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r, id, int(texelFetch(position_tex, tex_coord(position_tex, id), 0).w));\n"
|
||||
" color = color_base * (ambient + emission);\n"
|
||||
" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n"
|
||||
" world_position = pos;\n"
|
||||
" shadow_normal = eye_normal;\n"
|
||||
" segment_id = id_a;\n"
|
||||
" cut_color_direct = clipping_plane.xyz == vec3(0.0) ? vec3(0.0) :\n"
|
||||
" color_base * direct_lighting(eye_position, mat3(view_matrix) * -clipping_plane.xyz);\n"
|
||||
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
// ORCA: realistic view - shadow caster, one light facing ribbon per segment, not instanced as tiny instances are slow.
|
||||
static const char* Segments_Shadow_Caster_Vertex_Shader_ES =
|
||||
"#version 300 es\n"
|
||||
"precision lowp usampler2D;\n"
|
||||
"const vec3 UP = vec3(0, 0, 1);\n"
|
||||
"const int CORNERS[6] = int[](0, 1, 2, 0, 2, 3);\n"
|
||||
"uniform mat4 view_matrix;\n"
|
||||
"uniform mat4 projection_matrix;\n"
|
||||
"uniform sampler2D position_tex;\n"
|
||||
"uniform sampler2D height_width_angle_tex;\n"
|
||||
"uniform usampler2D segment_index_tex;\n"
|
||||
"uniform bool reverse_order;\n"
|
||||
"uniform int instances_count;\n"
|
||||
"out vec3 world_position;\n"
|
||||
"ivec2 tex_coord(sampler2D sampler, int id) {\n"
|
||||
" ivec2 tex_size = textureSize(sampler, 0);\n"
|
||||
" return (tex_size.y == 1) ? ivec2(id, 0) : ivec2(id % tex_size.x, id / tex_size.x);\n"
|
||||
"}\n"
|
||||
"ivec2 tex_coord_u(usampler2D sampler, int id) {\n"
|
||||
" ivec2 tex_size = textureSize(sampler, 0);\n"
|
||||
" return (tex_size.y == 1) ? ivec2(id, 0) : ivec2(id % tex_size.x, id / tex_size.x);\n"
|
||||
"}\n"
|
||||
"void main() {\n"
|
||||
" int segment = gl_VertexID / 6;\n"
|
||||
" int corner = CORNERS[gl_VertexID - 6 * segment];\n"
|
||||
" int instance = reverse_order ? instances_count - 1 - segment : segment;\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, tex_coord_u(segment_index_tex, instance), 0).r);\n"
|
||||
" int id = corner < 2 ? id_a : id_a + 1;\n"
|
||||
" vec3 pos_a = texelFetch(position_tex, tex_coord(position_tex, id_a), 0).xyz;\n"
|
||||
" vec3 pos_b = texelFetch(position_tex, tex_coord(position_tex, id_a + 1), 0).xyz;\n"
|
||||
" vec3 line = pos_b - pos_a;\n"
|
||||
" float line_len = length(line);\n"
|
||||
" vec3 line_dir = line_len < 1e-4 ? vec3(1.0, 0.0, 0.0) : line / line_len;\n"
|
||||
" vec3 line_right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n"
|
||||
" vec3 line_up_dir = normalize(cross(line_right_dir, line_dir));\n"
|
||||
" vec3 to_light = vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]);\n"
|
||||
" vec3 side_dir = cross(to_light, line_dir);\n"
|
||||
" side_dir = dot(side_dir, side_dir) < 1e-8 ? line_right_dir : normalize(side_dir);\n"
|
||||
" vec2 height_width = texelFetch(height_width_angle_tex, tex_coord(height_width_angle_tex, id), 0).xy;\n"
|
||||
" float half_extent = 0.5 * (height_width.y * abs(dot(line_right_dir, side_dir)) + height_width.x * abs(dot(line_up_dir, side_dir)));\n"
|
||||
" float along = (corner < 2 ? -0.5 : 0.5) * height_width.y;\n"
|
||||
" float side = (corner == 0 || corner == 3) ? -1.0 : 1.0;\n"
|
||||
" vec3 pos = (corner < 2 ? pos_a : pos_b) + along * line_dir + side * half_extent * side_dir;\n"
|
||||
" world_position = pos;\n"
|
||||
" gl_Position = projection_matrix * (view_matrix * vec4(pos, 1.0));\n"
|
||||
"}\n";
|
||||
|
||||
// ORCA: realistic view - the shadow caster writes depth only. What a section cuts away casts no shadow.
|
||||
static const char* Segments_Shadow_Caster_Fragment_Shader_ES =
|
||||
"#version 300 es\n"
|
||||
"precision highp float;\n"
|
||||
"uniform vec4 clipping_plane;\n"
|
||||
"in vec3 world_position;\n"
|
||||
"void main() {\n"
|
||||
" if (dot(vec4(world_position, 1.0), clipping_plane) < 0.0)\n"
|
||||
" discard;\n"
|
||||
"}\n";
|
||||
|
||||
static const char* Segments_Fragment_Shader_ES =
|
||||
"#version 300 es\n"
|
||||
"precision highp float;\n"
|
||||
// ORCA: section view - segment ids exceed mediump
|
||||
"precision highp int;\n"
|
||||
"// ORCA: sampler2D defaults to lowp in an ES fragment shader, far too coarse to compare\n"
|
||||
"// shadow map depths against.\n"
|
||||
"precision highp sampler2D;\n"
|
||||
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
|
||||
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
|
||||
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
|
||||
"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
|
||||
"uniform vec3 light_top_dir;\n"
|
||||
"uniform sampler2D shadow_map;\n"
|
||||
"uniform mat4 shadow_light_vp;\n"
|
||||
"uniform float shadow_intensity;\n"
|
||||
@@ -179,23 +260,33 @@ static const char* Segments_Fragment_Shader_ES =
|
||||
// passes in each mode is decided in GLCanvas3D::_render_gcode, not here.
|
||||
"uniform float exposure;\n"
|
||||
"uniform float saturation;\n"
|
||||
// ORCA: section view - cut faces are ray cast; data samplers stay lowp as in the vertex shader
|
||||
"uniform vec4 clipping_plane;\n"
|
||||
"uniform mat4 view_matrix;\n"
|
||||
"uniform mat4 projection_matrix;\n"
|
||||
"uniform vec3 camera_position;\n"
|
||||
"uniform lowp sampler2D position_tex;\n"
|
||||
"uniform lowp sampler2D height_width_angle_tex;\n"
|
||||
"const vec3 UP = vec3(0, 0, 1);\n"
|
||||
"const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n"
|
||||
"in vec3 color;\n"
|
||||
"in vec3 color_direct;\n"
|
||||
"in vec3 world_position;\n"
|
||||
"in vec3 shadow_normal;\n"
|
||||
"flat in int segment_id;\n"
|
||||
"in vec3 cut_color_direct;\n"
|
||||
"out vec4 fragment_color;\n"
|
||||
"float shadow_shade() {\n"
|
||||
"float shadow_shade(vec3 position, vec3 normal) {\n"
|
||||
" if (shadow_intensity <= 0.0)\n"
|
||||
" return 1.0;\n"
|
||||
" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n"
|
||||
" vec4 lp = shadow_light_vp * vec4(position, 1.0);\n"
|
||||
" vec3 proj = lp.xyz / lp.w;\n"
|
||||
" proj = proj * 0.5 + 0.5;\n"
|
||||
" if (proj.z > 1.0)\n"
|
||||
" return 1.0;\n"
|
||||
" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n"
|
||||
" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n"
|
||||
" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n"
|
||||
" float NdotL = dot(normalize(normal), light_top_dir);\n"
|
||||
" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n"
|
||||
" float sum = 0.0;\n"
|
||||
" for (int x = -2; x <= 2; ++x) {\n"
|
||||
@@ -206,8 +297,43 @@ static const char* Segments_Fragment_Shader_ES =
|
||||
" }\n"
|
||||
" return 1.0 - shadow_intensity * (sum / 25.0);\n"
|
||||
"}\n"
|
||||
"vec4 fetch(lowp sampler2D sampler, int id) {\n"
|
||||
" ivec2 tex_size = textureSize(sampler, 0);\n"
|
||||
" return texelFetch(sampler, (tex_size.y == 1) ? ivec2(id, 0) : ivec2(id % tex_size.x, id / tex_size.x), 0);\n"
|
||||
"}\n"
|
||||
// ORCA: section view - where the view ray meets the plane, if inside the extrusion's diamond section
|
||||
"bool find_cut(out vec3 cut) {\n"
|
||||
// parallel rays for an orthographic projection
|
||||
" vec3 ray = projection_matrix[3][3] == 1.0 ? -vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]) :\n"
|
||||
" normalize(world_position - camera_position);\n"
|
||||
" float approach = dot(clipping_plane.xyz, ray);\n"
|
||||
" if (approach <= 0.0)\n"
|
||||
" return false;\n"
|
||||
" cut = world_position - dot(vec4(world_position, 1.0), clipping_plane) / approach * ray;\n"
|
||||
" vec3 pos_a = fetch(position_tex, segment_id).xyz;\n"
|
||||
" vec3 line = fetch(position_tex, segment_id + 1).xyz - pos_a;\n"
|
||||
" float length_sq = dot(line, line);\n"
|
||||
" float along = length_sq < 1e-8 ? -1.0 : dot(cut - pos_a, line) / length_sq;\n"
|
||||
" if (along < 0.0 || along > 1.0)\n"
|
||||
" return false;\n"
|
||||
" vec3 line_dir = line * inversesqrt(length_sq);\n"
|
||||
" vec3 right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n"
|
||||
" vec3 offset = cut - pos_a - along * line;\n"
|
||||
" vec2 half_height_width = 0.5 * mix(fetch(height_width_angle_tex, segment_id).xy,\n"
|
||||
" fetch(height_width_angle_tex, segment_id + 1).xy, along);\n"
|
||||
" return abs(dot(offset, right_dir)) / half_height_width.y + abs(dot(offset, cross(right_dir, line_dir))) / half_height_width.x <= 1.0;\n"
|
||||
"}\n"
|
||||
"void main() {\n"
|
||||
" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n"
|
||||
" vec3 c;\n"
|
||||
" if (dot(vec4(world_position, 1.0), clipping_plane) >= 0.0)\n"
|
||||
" c = color + color_direct * shadow_shade(world_position, shadow_normal);\n"
|
||||
" else {\n"
|
||||
" vec3 cut;\n"
|
||||
" if (!find_cut(cut))\n"
|
||||
" discard;\n"
|
||||
" c = color + cut_color_direct * shadow_shade(cut, mat3(view_matrix) * -clipping_plane.xyz);\n"
|
||||
" }\n"
|
||||
" c *= exposure;\n"
|
||||
" c = mix(vec3(dot(c, LUMA)), c, saturation);\n"
|
||||
" fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n"
|
||||
"}\n";
|
||||
@@ -230,16 +356,28 @@ static const char* Options_Vertex_Shader_ES =
|
||||
"uniform sampler2D height_width_angle_tex;\n"
|
||||
"uniform sampler2D color_tex;\n"
|
||||
"uniform usampler2D segment_index_tex;\n"
|
||||
// ORCA: section view
|
||||
"uniform vec4 clipping_plane;\n"
|
||||
"in vec3 in_position;\n"
|
||||
"in vec3 in_normal;\n"
|
||||
"out vec3 color;\n"
|
||||
"vec3 decode_color(float color) {\n"
|
||||
"out float clipping_plane_dot;\n"
|
||||
// ORCA: the layers greyed or dimmed around the one the sliders show, see ViewerImpl::set_layer_colors().
|
||||
"uniform ivec2 lit_layers;\n"
|
||||
"uniform int grey_below_layer;\n"
|
||||
"uniform float dim_brightness;\n"
|
||||
"uniform int kept_vertex;\n"
|
||||
"vec3 decode_color(float color, int id, int layer) {\n"
|
||||
" int c = int(round(color));\n"
|
||||
" int r = (c >> 16) & 0xFF;\n"
|
||||
" int g = (c >> 8) & 0xFF;\n"
|
||||
" int b = (c >> 0) & 0xFF;\n"
|
||||
" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
|
||||
" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
|
||||
" if (dim_brightness >= 0.0)\n"
|
||||
" rgb = floor(rgb * dim_brightness);\n"
|
||||
" else if (layer < grey_below_layer)\n"
|
||||
" rgb = vec3(64.0);\n"
|
||||
" }\n"
|
||||
" float f = 1.0 / 255.0f;\n"
|
||||
" return f * vec3(r, g, b);\n"
|
||||
" return f * rgb;\n"
|
||||
"}\n"
|
||||
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
|
||||
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
|
||||
@@ -264,9 +402,11 @@ static const char* Options_Vertex_Shader_ES =
|
||||
" height_width.y, 0.0, 0.0,\n"
|
||||
" 0.0, height_width.y, 0.0,\n"
|
||||
" 0.0, 0.0, height_width.x);\n"
|
||||
// ORCA: section view - markers are cut away whole, by their centre
|
||||
" clipping_plane_dot = dot(vec4(offset, 1.0), clipping_plane);\n"
|
||||
" vec3 eye_position = (view_matrix * vec4(scale_matrix * in_position + offset, 1.0)).xyz;\n"
|
||||
" vec3 eye_normal = (view_matrix * vec4(in_normal, 0.0)).xyz;\n"
|
||||
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r);\n"
|
||||
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r, id, int(texelFetch(position_tex, tex_coord(position_tex, id), 0).w));\n"
|
||||
" color = color_base * lighting(eye_position, eye_normal);\n"
|
||||
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
|
||||
"}\n";
|
||||
@@ -275,8 +415,11 @@ static const char* Options_Fragment_Shader_ES =
|
||||
"#version 300 es\n"
|
||||
"precision highp float;\n"
|
||||
"in vec3 color;\n"
|
||||
"in float clipping_plane_dot;\n"
|
||||
"out vec4 fragment_color;\n"
|
||||
"void main() {\n"
|
||||
" if (clipping_plane_dot < 0.0)\n"
|
||||
" discard;\n"
|
||||
" fragment_color = vec4(color, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
|
||||