Compare commits

...
Author SHA1 Message Date
Ian BassiandRodrigo Faselli 222c6a2df5 Improve performance by migrating to Clipper2 2.0.1 (#15969)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-02 17:33:41 -03:00
Ian BassiandKris Austin 70bc02467b Faster Preview View (#15884)
Co-authored-by: Kris Austin <kris.austin@gmail.com>
2026-10-02 14:45:58 -03:00
Kris Austin 4ffba13210 ci: time out macOS notarization after 30 minutes (#16087)
notarytool submit --wait has no timeout. On 2026-10-02 it hung for
over 5 hours in a main build. Since #16044 a new push no longer
cancels a running main build, so nothing stopped it and six waiting
main runs were replaced without starting.

Over the last 30 days the step succeeded 206 times, with a median of
4.3 minutes and a maximum of 14.8.
2026-10-02 14:40:51 -03:00
Ian Bassi a1ad2b4425 Add section view feature for 3D canvas (#15879) 2026-10-02 11:50:43 -03:00
Noisyfox 6c6e8be43d Fix debug build cmake errors (#14593)
* Fix debug build after qhull upgrade

We upgraded qhull from 8.0.1 to 8.0.2 in 504a5d3b70, which contains a commit qhull/qhull@16159c648c `use same CMake target name for Debug and non-Debug`, so this target name check is no longer required

* Fix issue like `IMPORTED_LOCATION not set for imported target "opencv_world" configuration "RelWithDebInfo".` when build Debug config
2026-10-02 09:46:24 -03:00
138 changed files with 5362 additions and 7936 deletions
+1
View File
@@ -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 }}
+3 -1
View File
@@ -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})
-1
View File
@@ -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)
-20
View File
@@ -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
)
File diff suppressed because it is too large Load Diff
-606
View File
@@ -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
-7
View File
@@ -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"
-18
View File
@@ -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
+6 -1
View File
@@ -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);
}
File diff suppressed because it is too large Load Diff
@@ -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();
}
+1 -5
View File
@@ -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)
+131
View File
@@ -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`.
+93
View File
@@ -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.
+105
View File
@@ -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.
+1
View File
@@ -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
+1
View File
@@ -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

+1
View File
@@ -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

+5 -1
View File
@@ -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;
+6 -2
View File
@@ -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);
+7 -3
View File
@@ -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);
+4
View File
@@ -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;
+5 -1
View File
@@ -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;
+6 -2
View File
@@ -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);
+7 -3
View File
@@ -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);
+4
View File
@@ -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;
+2 -9
View File
@@ -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 {};
+1 -1
View File
@@ -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++;
+56 -93
View File
@@ -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 &params)
{
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
+6 -2
View File
@@ -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);
+3
View File
@@ -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"
+4 -15
View File
@@ -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
+10 -7
View File
@@ -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());
+1 -5
View File
@@ -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
-228
View File
@@ -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;
}
}
-22
View File
@@ -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
-167
View File
@@ -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_
File diff suppressed because it is too large Load Diff
+70 -156
View File
@@ -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.);
+77
View File
@@ -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
+17 -7
View File
@@ -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); };
}
+1 -1
View File
@@ -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"
+77 -6
View File
@@ -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());
+1 -1
View File
@@ -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);
+6 -25
View File
@@ -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);
+2 -2
View File
@@ -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);
+1 -8
View File
@@ -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)
+7 -19
View File
@@ -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)
-1
View File
@@ -4,7 +4,6 @@
#include "ClipperUtils.hpp"
#include "ExPolygon.hpp"
#include "Line.hpp"
#include "clipper.hpp"
#include <algorithm>
#include <cassert>
#include <cmath>
+3 -3
View File
@@ -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)
{
+9 -9
View File
@@ -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 &param)
{
// 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;
+29 -37
View File
@@ -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
+43 -10
View File
@@ -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;
+1 -1
View File
@@ -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)
{
+3 -3
View File
@@ -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());
+2 -2
View File
@@ -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,
+7 -8
View File
@@ -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(
+1 -1
View File
@@ -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());
+2 -18
View File
@@ -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);
+2 -2
View File
@@ -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);
+5 -5
View File
@@ -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)) << " ";
}
+4 -5
View File
@@ -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);
-17
View File
@@ -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)
{
-6
View File
@@ -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.
+41 -69
View File
@@ -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>());
+4 -4
View File
@@ -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);
+12 -12
View File
@@ -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();
}
+3 -3
View File
@@ -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]));
+8 -8
View File
@@ -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)
+3 -3
View File
@@ -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)
+3 -3
View File
@@ -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.
-13
View File
@@ -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>
-26
View File
@@ -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
-2
View File
@@ -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"
+2
View File
@@ -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:
+8
View File
@@ -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);
//
// ************************************************************************
+146 -27
View File
@@ -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";
+162 -19
View File
@@ -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";

Some files were not shown because too many files have changed in this diff Show More