2d-operations via Clipper2 (#341)
* WIP Clipper2 wrapper for 2d subsystem * Working example in clippoly_test * Add clean_ property to clippoly * Square center=false by default * Square defaults to center=false * Shorten union example * + simplification and minkowski functions * + Circle and note on Minkowski * + Offset (InflatePaths) * Clippoly -> CrossSection renaming * Switch from NonZero to Positive fill rule * -(minkowski, simplifs, clean_) + pre-alloc vecs Remove minkowski, simplification algorithms other than simplify, clean (union) arbitrary contours on construction, and pre-allocate vectors when length is known. * - index from polygon conversion + missed vec prealloc * Use Polygon shorthand over std::vector * Update copyright date * Add Rotate * Update copyrights to 2023 * Extrude and Revolve accept CrossSection only * Fix incorrect use of vector constructor * Fix remaining incorrect vector construction * Revert "Update copyrights to 2023" This reverts commit af8f6c8c95135eb3376e1f150fe720fddea16f33. * Re-update copyright notice in cross_section files * Add missing precision_ argument to cleaning Unions * Lift circular resolution globals into Quality * Replace uses of Manifold globals with Quality * + Ellipse, Mirror, Area * Include mirror in Union example and test area * + Rewind (reverse winding of all contours) * + fillrule/IsEmpty - Ellipse/Rewind * Move Quality into public.h * Remove bad definition * Replace manual circles with CrossSection::Circle * Lists of empty contours are empty * + Rect class, RectClip, + const params/methods * Add Transform to CrossSection and Rect
This commit is contained in:
@@ -11,3 +11,6 @@
|
||||
path = bindings/python/third_party/pybind11
|
||||
url = https://github.com/pybind/pybind11
|
||||
branch = stable
|
||||
[submodule "src/third_party/clipper2"]
|
||||
path = src/third_party/clipper2
|
||||
url = https://github.com/AngusJohnson/clipper2
|
||||
|
||||
@@ -10,7 +10,7 @@ endif()
|
||||
|
||||
target_link_libraries(
|
||||
manifoldc
|
||||
PRIVATE manifold sdf meshIO graphlite
|
||||
PRIVATE manifold sdf meshIO graphlite cross_section
|
||||
)
|
||||
|
||||
target_include_directories(manifoldc PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <conv.h>
|
||||
#include <cross_section.h>
|
||||
#include <manifold.h>
|
||||
#include <manifoldc.h>
|
||||
#include <meshIO.h>
|
||||
@@ -406,19 +407,19 @@ float *manifold_curvature_vert_gaussian(void *mem, ManifoldCurvature *curv) {
|
||||
|
||||
// Static Quality Globals
|
||||
void manifold_set_min_circular_angle(float degrees) {
|
||||
Manifold::SetMinCircularAngle(degrees);
|
||||
Quality::SetMinCircularAngle(degrees);
|
||||
}
|
||||
|
||||
void manifold_set_min_circular_edge_length(float length) {
|
||||
Manifold::SetMinCircularEdgeLength(length);
|
||||
Quality::SetMinCircularEdgeLength(length);
|
||||
}
|
||||
|
||||
void manifold_set_circular_segments(int number) {
|
||||
Manifold::SetCircularSegments(number);
|
||||
Quality::SetCircularSegments(number);
|
||||
}
|
||||
|
||||
int manifold_get_circular_segments(float radius) {
|
||||
return Manifold::GetCircularSegments(radius);
|
||||
return Quality::GetCircularSegments(radius);
|
||||
}
|
||||
|
||||
// memory size
|
||||
|
||||
@@ -19,7 +19,7 @@ add_executable(manifoldjs bindings.cpp)
|
||||
# make sure that we recompile the wasm when bindings.js is being modified
|
||||
set_source_files_properties(bindings.cpp OBJECT_DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
|
||||
target_link_libraries(manifoldjs manifold sdf)
|
||||
target_link_libraries(manifoldjs manifold sdf cross_section)
|
||||
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS} -fexceptions)
|
||||
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
|
||||
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1)
|
||||
|
||||
@@ -271,9 +271,9 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
function("_intersectionN", &IntersectionN);
|
||||
function("_Compose", &Manifold::Compose);
|
||||
|
||||
function("setMinCircularAngle", &Manifold::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Manifold::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Manifold::SetCircularSegments);
|
||||
function("getCircularSegments", &Manifold::GetCircularSegments);
|
||||
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Quality::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Quality::SetCircularSegments);
|
||||
function("getCircularSegments", &Quality::GetCircularSegments);
|
||||
function("reserveIDs", &Manifold::ReserveIDs);
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "samples.h"
|
||||
|
||||
namespace {
|
||||
@@ -23,12 +24,8 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
|
||||
int nCut, int nDivision) {
|
||||
Manifold base = Manifold::Cylinder(width, radius + twistRadius / 2);
|
||||
|
||||
Polygons circle(1);
|
||||
float dPhiDeg = 180.0f / nDivision;
|
||||
for (int i = 0; i < 2 * nDivision; ++i) {
|
||||
circle[0].push_back({decorRadius * cosd(dPhiDeg * i) + twistRadius,
|
||||
decorRadius * sind(dPhiDeg * i)});
|
||||
}
|
||||
CrossSection circle =
|
||||
CrossSection::Circle(decorRadius, nDivision).Translate({twistRadius, 0});
|
||||
Manifold decor = Manifold::Extrude(circle, width, nDivision, 180)
|
||||
.Scale({1.0f, 0.5f, 1.0f})
|
||||
.Translate({0.0f, radius, 0.0f});
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "samples.h"
|
||||
|
||||
namespace {
|
||||
@@ -33,7 +34,7 @@ namespace manifold {
|
||||
* @param minorRadius Radius of the small cross-section of the imaginary donut.
|
||||
* @param threadRadius Radius of the small cross-section of the actual object.
|
||||
* @param circularSegments Number of linear segments making up the threadRadius
|
||||
* circle. Default is Manifold.GetCircularSegments().
|
||||
* circle. Default is Quality.GetCircularSegments().
|
||||
* @param linearSegments Number of segments along the length of the knot.
|
||||
* Default makes roughly square facets.
|
||||
*/
|
||||
@@ -44,16 +45,11 @@ Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius,
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
int n = circularSegments > 2 ? circularSegments
|
||||
: Manifold::GetCircularSegments(threadRadius);
|
||||
: Quality::GetCircularSegments(threadRadius);
|
||||
int m =
|
||||
linearSegments > 2 ? linearSegments : n * q * majorRadius / threadRadius;
|
||||
|
||||
Polygons circle(1);
|
||||
float dPhi = 360.0f / n;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[0].push_back({cosd(dPhi * i) + 2.0f, sind(dPhi * i)});
|
||||
}
|
||||
|
||||
CrossSection circle = CrossSection::Circle(1., n).Translate({2, 0});
|
||||
Manifold knot = Manifold::Revolve(circle, m);
|
||||
|
||||
knot =
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace manifold {
|
||||
* @param edgeLength Distance between the corners.
|
||||
* @param radius Radius of the frame members.
|
||||
* @param circularSegments Number of segments in the cylinders and spheres.
|
||||
* Defaults to Manifold.GetCircularSegments().
|
||||
* Defaults to Quality.GetCircularSegments().
|
||||
*/
|
||||
Manifold RoundedFrame(float edgeLength, float radius, int circularSegments) {
|
||||
Manifold edge = Manifold::Cylinder(edgeLength, radius, -1, circularSegments);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
add_subdirectory(third_party)
|
||||
add_subdirectory(utilities)
|
||||
add_subdirectory(collider)
|
||||
add_subdirectory(cross_section)
|
||||
add_subdirectory(polygon)
|
||||
add_subdirectory(manifold)
|
||||
add_subdirectory(sdf)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Copyright 2023 The Manifold Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
project (cross_section)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
target_link_libraries( ${PROJECT_NAME}
|
||||
PUBLIC utilities Clipper2
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <clipper2/clipper.h>
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "glm/ext/vector_float2.hpp"
|
||||
#include "public.h"
|
||||
|
||||
namespace C2 = Clipper2Lib;
|
||||
|
||||
namespace manifold {
|
||||
|
||||
class Rect;
|
||||
|
||||
class CrossSection {
|
||||
public:
|
||||
CrossSection();
|
||||
~CrossSection();
|
||||
CrossSection(const CrossSection& other);
|
||||
CrossSection& operator=(const CrossSection& other);
|
||||
CrossSection(CrossSection&&) noexcept;
|
||||
CrossSection& operator=(CrossSection&&) noexcept;
|
||||
|
||||
// Construction from arbitrary contours
|
||||
enum class FillRule { EvenOdd, NonZero, Positive, Negative };
|
||||
CrossSection(const SimplePolygon& contour,
|
||||
FillRule fillrule = FillRule::Positive);
|
||||
CrossSection(const Polygons& contours,
|
||||
FillRule fillrule = FillRule::Positive);
|
||||
|
||||
// Shapes
|
||||
static CrossSection Square(const glm::vec2 dims, bool center = false);
|
||||
static CrossSection Circle(float radius, int circularSegments = 0);
|
||||
|
||||
// Booleans
|
||||
enum class OpType { Add, Subtract, Intersect, Xor };
|
||||
CrossSection Boolean(const CrossSection& second, OpType op) const;
|
||||
static CrossSection BatchBoolean(
|
||||
const std::vector<CrossSection>& crossSections, OpType op);
|
||||
CrossSection operator+(const CrossSection&) const; // Add (Union)
|
||||
CrossSection& operator+=(const CrossSection&);
|
||||
CrossSection operator-(const CrossSection&) const; // Subtract (Difference)
|
||||
CrossSection& operator-=(const CrossSection&);
|
||||
CrossSection operator^(const CrossSection&) const; // Intersect
|
||||
CrossSection& operator^=(const CrossSection&);
|
||||
CrossSection RectClip(const Rect& rect) const;
|
||||
|
||||
// Transformations
|
||||
CrossSection Translate(const glm::vec2 v) const;
|
||||
CrossSection Rotate(float degrees) const;
|
||||
CrossSection Scale(const glm::vec2 s) const;
|
||||
CrossSection Mirror(const glm::vec2 ax) const;
|
||||
CrossSection Transform(const glm::mat3x2& m) const;
|
||||
|
||||
// Path Simplification
|
||||
CrossSection Simplify(double epsilon = 1e-6) const;
|
||||
|
||||
// Offsetting
|
||||
enum class JoinType { Square, Round, Miter };
|
||||
CrossSection Offset(double delta, JoinType jt, double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0) const;
|
||||
|
||||
// Geometry
|
||||
double Area() const;
|
||||
bool IsEmpty() const;
|
||||
Rect Bounds() const;
|
||||
|
||||
// Output
|
||||
Polygons ToPolygons() const;
|
||||
|
||||
private:
|
||||
C2::PathsD paths_;
|
||||
CrossSection(C2::PathsD paths);
|
||||
};
|
||||
|
||||
class Rect {
|
||||
public:
|
||||
glm::vec2 min = glm::vec2(0);
|
||||
glm::vec2 max = glm::vec2(0);
|
||||
|
||||
/**
|
||||
* Default constructor is an empty rectangle..
|
||||
*/
|
||||
Rect();
|
||||
~Rect();
|
||||
Rect(const Rect& other);
|
||||
Rect& operator=(const Rect& other);
|
||||
Rect(Rect&&) noexcept;
|
||||
Rect& operator=(Rect&&) noexcept;
|
||||
|
||||
/**
|
||||
* Creates a rectangle that contains the two given points.
|
||||
*/
|
||||
Rect(const glm::vec2 a, const glm::vec2 b);
|
||||
|
||||
/**
|
||||
* Returns the dimensions of the Rect.
|
||||
*/
|
||||
glm::vec2 Size() const;
|
||||
|
||||
/**
|
||||
* Returns the absolute-largest coordinate value of any contained
|
||||
* point.
|
||||
*/
|
||||
float Scale() const;
|
||||
|
||||
/**
|
||||
* Returns the center point of the Box.
|
||||
*/
|
||||
glm::vec2 Center() const;
|
||||
|
||||
bool Contains(const glm::vec2& pt) const;
|
||||
bool Contains(const Rect& other) const;
|
||||
bool DoesOverlap(const Rect& other) const;
|
||||
bool IsEmpty() const;
|
||||
bool IsFinite() const;
|
||||
|
||||
/**
|
||||
* Expand this rectangle (in place) to include the given point.
|
||||
*/
|
||||
void Union(const glm::vec2 p);
|
||||
|
||||
/**
|
||||
* Expand this rectangle to include the given Rect.
|
||||
*/
|
||||
Rect Union(const Rect& other) const;
|
||||
|
||||
/**
|
||||
* Shift this rectangle by the given vector.
|
||||
*/
|
||||
Rect operator+(const glm::vec2 shift) const;
|
||||
|
||||
/**
|
||||
* Shift this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& operator+=(const glm::vec2 shift);
|
||||
|
||||
/**
|
||||
* Scale this rectangle by the given vector.
|
||||
*/
|
||||
Rect operator*(const glm::vec2 scale) const;
|
||||
|
||||
/**
|
||||
* Scale this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& operator*=(const glm::vec2 scale);
|
||||
|
||||
Rect Transform(const glm::mat3x2& m) const;
|
||||
|
||||
CrossSection AsCrossSection() const;
|
||||
};
|
||||
} // namespace manifold
|
||||
@@ -0,0 +1,439 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
|
||||
#include <clipper2/clipper.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "glm/ext/vector_float2.hpp"
|
||||
#include "glm/geometric.hpp"
|
||||
#include "glm/glm.hpp"
|
||||
#include "public.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
const int precision_ = 8;
|
||||
|
||||
C2::ClipType cliptype_of_op(CrossSection::OpType op) {
|
||||
C2::ClipType ct = C2::ClipType::Union;
|
||||
switch (op) {
|
||||
case CrossSection::OpType::Add:
|
||||
break;
|
||||
case CrossSection::OpType::Subtract:
|
||||
ct = C2::ClipType::Difference;
|
||||
break;
|
||||
case CrossSection::OpType::Intersect:
|
||||
ct = C2::ClipType::Intersection;
|
||||
break;
|
||||
case CrossSection::OpType::Xor:
|
||||
ct = C2::ClipType::Xor;
|
||||
break;
|
||||
};
|
||||
return ct;
|
||||
}
|
||||
|
||||
C2::FillRule fr(CrossSection::FillRule fillrule) {
|
||||
C2::FillRule fr = C2::FillRule::EvenOdd;
|
||||
switch (fillrule) {
|
||||
case CrossSection::FillRule::EvenOdd:
|
||||
break;
|
||||
case CrossSection::FillRule::NonZero:
|
||||
fr = C2::FillRule::NonZero;
|
||||
break;
|
||||
case CrossSection::FillRule::Positive:
|
||||
fr = C2::FillRule::Positive;
|
||||
break;
|
||||
case CrossSection::FillRule::Negative:
|
||||
fr = C2::FillRule::Negative;
|
||||
break;
|
||||
};
|
||||
return fr;
|
||||
}
|
||||
|
||||
C2::JoinType jt(CrossSection::JoinType jointype) {
|
||||
C2::JoinType jt = C2::JoinType::Square;
|
||||
switch (jointype) {
|
||||
case CrossSection::JoinType::Square:
|
||||
break;
|
||||
case CrossSection::JoinType::Round:
|
||||
jt = C2::JoinType::Round;
|
||||
break;
|
||||
case CrossSection::JoinType::Miter:
|
||||
jt = C2::JoinType::Miter;
|
||||
break;
|
||||
};
|
||||
return jt;
|
||||
}
|
||||
|
||||
glm::vec2 v2_of_pd(const C2::PointD p) { return {p.x, p.y}; }
|
||||
|
||||
C2::PointD v2_to_pd(const glm::vec2 v) { return C2::PointD(v.x, v.y); }
|
||||
|
||||
C2::PathD pathd_of_contour(const SimplePolygon& ctr) {
|
||||
auto p = C2::PathD();
|
||||
p.reserve(ctr.size());
|
||||
for (auto v : ctr) {
|
||||
p.push_back(v2_to_pd(v));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
CrossSection::CrossSection() { paths_ = C2::PathsD(); }
|
||||
CrossSection::~CrossSection() = default;
|
||||
CrossSection::CrossSection(CrossSection&&) noexcept = default;
|
||||
CrossSection& CrossSection::operator=(CrossSection&&) noexcept = default;
|
||||
CrossSection::CrossSection(const CrossSection& other) {
|
||||
paths_ = C2::PathsD(other.paths_);
|
||||
}
|
||||
CrossSection::CrossSection(C2::PathsD ps) { paths_ = ps; }
|
||||
|
||||
CrossSection::CrossSection(const SimplePolygon& contour, FillRule fillrule) {
|
||||
auto ps = C2::PathsD{(pathd_of_contour(contour))};
|
||||
paths_ = C2::Union(ps, fr(fillrule), precision_);
|
||||
}
|
||||
|
||||
CrossSection::CrossSection(const Polygons& contours, FillRule fillrule) {
|
||||
auto ps = C2::PathsD();
|
||||
ps.reserve(contours.size());
|
||||
for (auto ctr : contours) {
|
||||
ps.push_back(pathd_of_contour(ctr));
|
||||
}
|
||||
paths_ = C2::Union(ps, fr(fillrule), precision_);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Square(const glm::vec2 dims, bool center) {
|
||||
auto p = C2::PathD(4);
|
||||
if (center) {
|
||||
auto w = dims.x / 2;
|
||||
auto h = dims.y / 2;
|
||||
p[0] = C2::PointD(w, h);
|
||||
p[1] = C2::PointD(-w, h);
|
||||
p[2] = C2::PointD(-w, -h);
|
||||
p[3] = C2::PointD(w, -h);
|
||||
} else {
|
||||
double x = dims.x;
|
||||
double y = dims.y;
|
||||
p[0] = C2::PointD(0.0, 0.0);
|
||||
p[1] = C2::PointD(x, 0.0);
|
||||
p[2] = C2::PointD(x, y);
|
||||
p[3] = C2::PointD(0.0, y);
|
||||
}
|
||||
return CrossSection(C2::PathsD{p});
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Circle(float radius, int circularSegments) {
|
||||
int n = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(radius);
|
||||
float dPhi = 360.0f / n;
|
||||
auto circle = C2::PathD(n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[i] = C2::PointD(radius * cosd(dPhi * i), radius * sind(dPhi * i));
|
||||
}
|
||||
return CrossSection(C2::PathsD{circle});
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Boolean(const CrossSection& second,
|
||||
OpType op) const {
|
||||
auto ct = cliptype_of_op(op);
|
||||
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, paths_, second.paths_,
|
||||
precision_);
|
||||
return CrossSection(res);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::BatchBoolean(
|
||||
const std::vector<CrossSection>& crossSections, OpType op) {
|
||||
if (crossSections.size() == 0)
|
||||
return CrossSection();
|
||||
else if (crossSections.size() == 1)
|
||||
return crossSections[0];
|
||||
|
||||
auto subjs = crossSections[0].paths_;
|
||||
int n_clips = 0;
|
||||
for (int i = 1; i < crossSections.size(); ++i) {
|
||||
n_clips += crossSections[i].paths_.size();
|
||||
}
|
||||
auto clips = C2::PathsD();
|
||||
clips.reserve(n_clips);
|
||||
for (int i = 1; i < crossSections.size(); ++i) {
|
||||
auto ps = crossSections[i].paths_;
|
||||
clips.insert(clips.end(), ps.begin(), ps.end());
|
||||
}
|
||||
|
||||
auto ct = cliptype_of_op(op);
|
||||
auto res =
|
||||
C2::BooleanOp(ct, C2::FillRule::Positive, subjs, clips, precision_);
|
||||
return CrossSection(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for Boolean Union.
|
||||
*/
|
||||
CrossSection CrossSection::operator+(const CrossSection& Q) const {
|
||||
return Boolean(Q, OpType::Add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for Boolean Union assignment.
|
||||
*/
|
||||
CrossSection& CrossSection::operator+=(const CrossSection& Q) {
|
||||
*this = *this + Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for Boolean Difference.
|
||||
*/
|
||||
CrossSection CrossSection::operator-(const CrossSection& Q) const {
|
||||
return Boolean(Q, OpType::Subtract);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for Boolean Difference assignment.
|
||||
*/
|
||||
CrossSection& CrossSection::operator-=(const CrossSection& Q) {
|
||||
*this = *this - Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for Boolean Intersection.
|
||||
*/
|
||||
CrossSection CrossSection::operator^(const CrossSection& Q) const {
|
||||
return Boolean(Q, OpType::Intersect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for Boolean Intersection assignment.
|
||||
*/
|
||||
CrossSection& CrossSection::operator^=(const CrossSection& Q) {
|
||||
*this = *this ^ Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CrossSection CrossSection::RectClip(const Rect& rect) const {
|
||||
auto r = C2::RectD(rect.min.x, rect.min.y, rect.max.x, rect.max.y);
|
||||
auto ps = C2::RectClip(r, paths_, false, precision_);
|
||||
return CrossSection(ps);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Translate(const glm::vec2 v) const {
|
||||
auto ps = C2::TranslatePaths(paths_, v.x, v.y);
|
||||
return CrossSection(ps);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Rotate(float degrees) const {
|
||||
auto rotated = C2::PathsD();
|
||||
rotated.reserve(paths_.size());
|
||||
auto s = sind(degrees);
|
||||
auto c = cosd(degrees);
|
||||
for (auto path : paths_) {
|
||||
auto r = C2::PathD();
|
||||
r.reserve(path.size());
|
||||
for (auto p : path) {
|
||||
auto rx = (p.x * c) - (p.y * s);
|
||||
auto ry = (p.y * c) + (p.x * s);
|
||||
r.push_back(C2::PointD(rx, ry));
|
||||
}
|
||||
rotated.push_back(r);
|
||||
}
|
||||
return CrossSection(rotated);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Scale(const glm::vec2 scale) const {
|
||||
auto scaled = C2::PathsD();
|
||||
scaled.reserve(paths_.size());
|
||||
for (auto path : paths_) {
|
||||
auto s = C2::PathD();
|
||||
s.reserve(path.size());
|
||||
for (auto p : path) {
|
||||
s.push_back(C2::PointD(p.x * scale.x, p.y * scale.y));
|
||||
}
|
||||
scaled.push_back(s);
|
||||
}
|
||||
return CrossSection(scaled);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Mirror(const glm::vec2 ax) const {
|
||||
if (glm::length(ax) == 0.) {
|
||||
return CrossSection();
|
||||
}
|
||||
auto mirrored = C2::PathsD();
|
||||
mirrored.reserve(paths_.size());
|
||||
for (auto path : paths_) {
|
||||
auto sz = path.size();
|
||||
auto m = C2::PathD(sz);
|
||||
for (int i = 0; i < sz; ++i) {
|
||||
auto v = v2_of_pd(path[sz - 1 - i]);
|
||||
m[i] = v2_to_pd(ax * (2 * glm::dot(v, ax) / glm::dot(ax, ax)) - v);
|
||||
}
|
||||
mirrored.push_back(m);
|
||||
}
|
||||
return CrossSection(mirrored);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Transform(const glm::mat3x2& m) const {
|
||||
auto transformed = C2::PathsD();
|
||||
transformed.reserve(paths_.size());
|
||||
for (auto path : paths_) {
|
||||
auto s = C2::PathD();
|
||||
s.reserve(path.size());
|
||||
for (auto p : path) {
|
||||
s.push_back(v2_to_pd(m * glm::vec3(p.x, p.y, 1)));
|
||||
}
|
||||
transformed.push_back(s);
|
||||
}
|
||||
return CrossSection(transformed);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Simplify(double epsilon) const {
|
||||
auto ps = SimplifyPaths(paths_, epsilon, false);
|
||||
return CrossSection(ps);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Offset(double delta, JoinType jointype,
|
||||
double miter_limit,
|
||||
double arc_tolerance) const {
|
||||
auto ps = C2::InflatePaths(paths_, delta, jt(jointype), C2::EndType::Polygon,
|
||||
miter_limit, precision_, arc_tolerance);
|
||||
return CrossSection(ps);
|
||||
}
|
||||
|
||||
double CrossSection::Area() const { return C2::Area(paths_); }
|
||||
Rect CrossSection::Bounds() const {
|
||||
auto r = C2::GetBounds(paths_);
|
||||
return Rect({r.left, r.bottom}, {r.right, r.top});
|
||||
}
|
||||
bool CrossSection::IsEmpty() const { return paths_.empty(); }
|
||||
|
||||
Polygons CrossSection::ToPolygons() const {
|
||||
auto polys = Polygons();
|
||||
polys.reserve(paths_.size());
|
||||
for (auto p : paths_) {
|
||||
auto sp = SimplePolygon();
|
||||
sp.reserve(paths_.size());
|
||||
for (auto v : p) {
|
||||
sp.push_back({v.x, v.y});
|
||||
}
|
||||
polys.push_back(sp);
|
||||
}
|
||||
return polys;
|
||||
}
|
||||
|
||||
// Rect
|
||||
|
||||
Rect::Rect() {}
|
||||
Rect::~Rect() = default;
|
||||
Rect::Rect(Rect&&) noexcept = default;
|
||||
Rect& Rect::operator=(Rect&&) noexcept = default;
|
||||
Rect::Rect(const Rect& other) {
|
||||
min = glm::vec2(other.min);
|
||||
max = glm::vec2(other.max);
|
||||
}
|
||||
|
||||
Rect::Rect(const glm::vec2 a, const glm::vec2 b) {
|
||||
min = glm::min(a, b);
|
||||
max = glm::max(a, b);
|
||||
}
|
||||
glm::vec2 Rect::Size() const { return max - min; }
|
||||
|
||||
float Rect::Scale() const {
|
||||
glm::vec2 absMax = glm::max(glm::abs(min), glm::abs(max));
|
||||
return glm::max(absMax.x, absMax.y);
|
||||
}
|
||||
|
||||
glm::vec2 Rect::Center() const { return 0.5f * (max + min); }
|
||||
|
||||
bool Rect::Contains(const glm::vec2& p) const {
|
||||
return glm::all(glm::greaterThanEqual(p, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, p));
|
||||
}
|
||||
|
||||
bool Rect::Contains(const Rect& rect) const {
|
||||
return glm::all(glm::greaterThanEqual(rect.min, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, rect.max));
|
||||
}
|
||||
|
||||
void Rect::Union(const glm::vec2 p) {
|
||||
min = glm::min(min, p);
|
||||
max = glm::max(max, p);
|
||||
}
|
||||
|
||||
Rect Rect::Union(const Rect& rect) const {
|
||||
Rect out;
|
||||
out.min = glm::min(min, rect.min);
|
||||
out.max = glm::max(max, rect.max);
|
||||
return out;
|
||||
}
|
||||
|
||||
Rect Rect::operator+(const glm::vec2 shift) const {
|
||||
Rect out;
|
||||
out.min = min + shift;
|
||||
out.max = max + shift;
|
||||
return out;
|
||||
}
|
||||
|
||||
Rect& Rect::operator+=(const glm::vec2 shift) {
|
||||
min += shift;
|
||||
max += shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rect Rect::operator*(const glm::vec2 scale) const {
|
||||
Rect out;
|
||||
out.min = min * scale;
|
||||
out.max = max * scale;
|
||||
return out;
|
||||
}
|
||||
|
||||
Rect& Rect::operator*=(const glm::vec2 scale) {
|
||||
min *= scale;
|
||||
max *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rect Rect::Transform(const glm::mat3x2& m) const {
|
||||
Rect rect;
|
||||
rect.min = m * glm::vec3(min, 1);
|
||||
rect.max = m * glm::vec3(max, 1);
|
||||
return rect;
|
||||
}
|
||||
|
||||
bool Rect::DoesOverlap(const Rect& rect) const {
|
||||
return min.x <= rect.max.x && min.y <= rect.max.y && max.x >= rect.min.x &&
|
||||
max.y >= rect.min.y;
|
||||
}
|
||||
|
||||
bool Rect::IsEmpty() const { return max.y <= min.y || max.x <= min.x; };
|
||||
bool Rect::IsFinite() const {
|
||||
return glm::all(glm::isfinite(min)) && glm::all(glm::isfinite(max));
|
||||
}
|
||||
|
||||
CrossSection Rect::AsCrossSection() const {
|
||||
SimplePolygon p(4);
|
||||
p[0] = glm::vec2(min.x, max.y);
|
||||
p[1] = glm::vec2(max.x, max.y);
|
||||
p[2] = glm::vec2(max.x, min.y);
|
||||
p[3] = glm::vec2(min.x, min.y);
|
||||
return CrossSection(p);
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
@@ -24,8 +24,8 @@ endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite
|
||||
PUBLIC utilities cross_section
|
||||
PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite Clipper2
|
||||
)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "public.h"
|
||||
|
||||
namespace manifold {
|
||||
@@ -62,10 +63,10 @@ class Manifold {
|
||||
float radiusHigh = -1.0f, int circularSegments = 0,
|
||||
bool center = false);
|
||||
static Manifold Sphere(float radius, int circularSegments = 0);
|
||||
static Manifold Extrude(Polygons crossSection, float height,
|
||||
static Manifold Extrude(const CrossSection& crossSection, float height,
|
||||
int nDivisions = 0, float twistDegrees = 0.0f,
|
||||
glm::vec2 scaleTop = glm::vec2(1.0f));
|
||||
static Manifold Revolve(const Polygons& crossSection,
|
||||
static Manifold Revolve(const CrossSection& crossSection,
|
||||
int circularSegments = 0);
|
||||
///@}
|
||||
|
||||
@@ -80,21 +81,6 @@ class Manifold {
|
||||
std::vector<Manifold> Decompose() const;
|
||||
///@}
|
||||
|
||||
/** @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
static void SetMinCircularAngle(float degrees);
|
||||
static void SetMinCircularEdgeLength(float length);
|
||||
static void SetCircularSegments(int number);
|
||||
static int GetCircularSegments(float radius);
|
||||
///@}
|
||||
|
||||
/** @name Information
|
||||
* Details of the manifold
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <thrust/sequence.h>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "csg_tree.h"
|
||||
#include "graph.h"
|
||||
#include "impl.h"
|
||||
@@ -171,12 +172,10 @@ Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh,
|
||||
int circularSegments, bool center) {
|
||||
float scale = radiusHigh >= 0.0f ? radiusHigh / radiusLow : 1.0f;
|
||||
float radius = fmax(radiusLow, radiusHigh);
|
||||
int n = circularSegments > 2 ? circularSegments : GetCircularSegments(radius);
|
||||
Polygons circle(1);
|
||||
float dPhi = 360.0f / n;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[0].push_back(radiusLow * glm::vec2(cosd(dPhi * i), sind(dPhi * i)));
|
||||
}
|
||||
int n = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(radius);
|
||||
|
||||
CrossSection circle = CrossSection::Circle(radiusLow, n);
|
||||
Manifold cylinder =
|
||||
Manifold::Extrude(circle, height, 0, 0.0f, glm::vec2(scale));
|
||||
if (center)
|
||||
@@ -197,7 +196,7 @@ Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh,
|
||||
*/
|
||||
Manifold Manifold::Sphere(float radius, int circularSegments) {
|
||||
int n = circularSegments > 0 ? (circularSegments + 3) / 4
|
||||
: GetCircularSegments(radius) / 4;
|
||||
: Quality::GetCircularSegments(radius) / 4;
|
||||
auto pImpl_ = std::make_shared<Impl>(Impl::Shape::Octahedron);
|
||||
pImpl_->Subdivide(n);
|
||||
for_each_n(autoPolicy(pImpl_->NumVert()), pImpl_->vertPos_.begin(),
|
||||
@@ -223,8 +222,11 @@ Manifold Manifold::Sphere(float radius, int circularSegments) {
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
|
||||
* Default {1, 1}.
|
||||
*/
|
||||
Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
float twistDegrees, glm::vec2 scaleTop) {
|
||||
Manifold Manifold::Extrude(const CrossSection& crossSection, float height,
|
||||
int nDivisions, float twistDegrees,
|
||||
glm::vec2 scaleTop) {
|
||||
auto polygons = crossSection.ToPolygons();
|
||||
|
||||
scaleTop.x = glm::max(scaleTop.x, 0.0f);
|
||||
scaleTop.y = glm::max(scaleTop.y, 0.0f);
|
||||
|
||||
@@ -236,15 +238,15 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
int nCrossSection = 0;
|
||||
bool isCone = scaleTop.x == 0.0 && scaleTop.y == 0.0;
|
||||
int idx = 0;
|
||||
PolygonsIdx crossSectionIndexed;
|
||||
for (auto& poly : crossSection) {
|
||||
PolygonsIdx polygonsIndexed;
|
||||
for (auto& poly : polygons) {
|
||||
nCrossSection += poly.size();
|
||||
SimplePolygonIdx simpleIndexed;
|
||||
for (const glm::vec2& polyVert : poly) {
|
||||
vertPos.push_back({polyVert.x, polyVert.y, 0.0f});
|
||||
simpleIndexed.push_back({polyVert, idx++});
|
||||
}
|
||||
crossSectionIndexed.push_back(simpleIndexed);
|
||||
polygonsIndexed.push_back(simpleIndexed);
|
||||
}
|
||||
for (int i = 1; i < nDivisions + 1; ++i) {
|
||||
float alpha = i / float(nDivisions);
|
||||
@@ -254,7 +256,7 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
transform = transform * glm::mat2(scale.x, 0.0f, 0.0f, scale.y);
|
||||
int j = 0;
|
||||
int idx = 0;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (const auto& poly : polygons) {
|
||||
for (int vert = 0; vert < poly.size(); ++vert) {
|
||||
int offset = idx + nCrossSection * i;
|
||||
int thisVert = vert + offset;
|
||||
@@ -275,9 +277,9 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
}
|
||||
}
|
||||
if (isCone)
|
||||
for (int j = 0; j < crossSection.size(); ++j) // Duplicate vertex for Genus
|
||||
for (int j = 0; j < polygons.size(); ++j) // Duplicate vertex for Genus
|
||||
vertPos.push_back({0.0f, 0.0f, height});
|
||||
std::vector<glm::ivec3> top = Triangulate(crossSectionIndexed);
|
||||
std::vector<glm::ivec3> top = Triangulate(polygonsIndexed);
|
||||
for (const glm::ivec3& tri : top) {
|
||||
triVerts.push_back({tri[0], tri[2], tri[1]});
|
||||
if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions);
|
||||
@@ -302,21 +304,24 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
Manifold Manifold::Revolve(const CrossSection& crossSection,
|
||||
int circularSegments) {
|
||||
auto polygons = crossSection.ToPolygons();
|
||||
|
||||
float radius = 0.0f;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (const auto& poly : polygons) {
|
||||
for (const auto& vert : poly) {
|
||||
radius = fmax(radius, vert.x);
|
||||
}
|
||||
}
|
||||
int nDivisions =
|
||||
circularSegments > 2 ? circularSegments : GetCircularSegments(radius);
|
||||
int nDivisions = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(radius);
|
||||
auto pImpl_ = std::make_shared<Impl>();
|
||||
auto& vertPos = pImpl_->vertPos_;
|
||||
VecDH<glm::ivec3> triVertsDH;
|
||||
auto& triVerts = triVertsDH;
|
||||
float dPhi = 360.0f / nDivisions;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (const auto& poly : polygons) {
|
||||
int start = -1;
|
||||
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
|
||||
if (poly[polyVert].x <= 0) {
|
||||
|
||||
@@ -306,66 +306,6 @@ MeshGL Manifold::GetMeshGL(glm::ivec3 normalIdx) const {
|
||||
return out;
|
||||
}
|
||||
|
||||
int Manifold::circularSegments_ = 0;
|
||||
float Manifold::circularAngle_ = 10.0f;
|
||||
float Manifold::circularEdgeLength_ = 1.0f;
|
||||
|
||||
/**
|
||||
* Sets an angle constraint the default number of circular segments for the
|
||||
* Cylinder(), Sphere(), and Revolve() constructors. The number of segments will
|
||||
* be rounded up to the nearest factor of four.
|
||||
*
|
||||
* @param angle The minimum angle in degrees between consecutive segments. The
|
||||
* angle will increase if the the segments hit the minimum edge length. Default
|
||||
* is 10 degrees.
|
||||
*/
|
||||
void Manifold::SetMinCircularAngle(float angle) {
|
||||
if (angle <= 0) return;
|
||||
Manifold::circularAngle_ = angle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a length constraint the default number of circular segments for the
|
||||
* Cylinder(), Sphere(), and Revolve() constructors. The number of segments will
|
||||
* be rounded up to the nearest factor of four.
|
||||
*
|
||||
* @param length The minimum length of segments. The length will
|
||||
* increase if the the segments hit the minimum angle. Default is 1.0.
|
||||
*/
|
||||
void Manifold::SetMinCircularEdgeLength(float length) {
|
||||
if (length <= 0) return;
|
||||
Manifold::circularEdgeLength_ = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default number of circular segments for the
|
||||
* Cylinder(), Sphere(), and Revolve() constructors. Overrides the edge length
|
||||
* and angle constraints and sets the number of segments to exactly this value.
|
||||
*
|
||||
* @param number Number of circular segments. Default is 0, meaning no
|
||||
* constraint is applied.
|
||||
*/
|
||||
void Manifold::SetCircularSegments(int number) {
|
||||
if (number < 3 && number != 0) return;
|
||||
Manifold::circularSegments_ = number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the result of the SetMinCircularAngle(),
|
||||
* SetMinCircularEdgeLength(), and SetCircularSegments() defaults.
|
||||
*
|
||||
* @param radius For a given radius of circle, determine how many default
|
||||
* segments there will be.
|
||||
*/
|
||||
int Manifold::GetCircularSegments(float radius) {
|
||||
if (Manifold::circularSegments_ > 0) return Manifold::circularSegments_;
|
||||
int nSegA = 360.0f / Manifold::circularAngle_;
|
||||
int nSegL = 2.0f * radius * glm::pi<float>() / Manifold::circularEdgeLength_;
|
||||
int nSeg = fmin(nSegA, nSegL) + 3;
|
||||
nSeg -= nSeg % 4;
|
||||
return nSeg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
|
||||
Vendored
+7
@@ -1 +1,8 @@
|
||||
add_subdirectory(graphlite)
|
||||
|
||||
set(CLIPPER2_UTILS OFF)
|
||||
set(CLIPPER2_EXAMPLES OFF)
|
||||
set(CLIPPER2_TESTS OFF)
|
||||
add_definitions(-D_HAS_EXCEPTIONS=0) # disable exceptions for STL
|
||||
|
||||
add_subdirectory(clipper2/CPP)
|
||||
|
||||
+1
Submodule src/third_party/clipper2 added at e9bd409439
@@ -131,6 +131,7 @@ using SimplePolygon = std::vector<glm::vec2>;
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
using Polygons = std::vector<SimplePolygon>;
|
||||
// };
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
@@ -422,6 +423,83 @@ struct Box {
|
||||
}
|
||||
};
|
||||
|
||||
class Quality {
|
||||
private:
|
||||
inline static int circularSegments_ = 0;
|
||||
inline static float circularAngle_ = 10.0f;
|
||||
inline static float circularEdgeLength_ = 1.0f;
|
||||
|
||||
public:
|
||||
/** @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
/**
|
||||
* Sets an angle constraint the default number of circular segments for the
|
||||
* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and
|
||||
* Manifold::Revolve() constructors. The number of segments will be rounded up
|
||||
* to the nearest factor of four.
|
||||
*
|
||||
* @param angle The minimum angle in degrees between consecutive segments. The
|
||||
* angle will increase if the the segments hit the minimum edge length.
|
||||
* Default is 10 degrees.
|
||||
*/
|
||||
static void SetMinCircularAngle(float angle) {
|
||||
if (angle <= 0) return;
|
||||
circularAngle_ = angle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a length constraint the default number of circular segments for the
|
||||
* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and
|
||||
* Manifold::Revolve() constructors. The number of segments will be rounded up
|
||||
* to the nearest factor of four.
|
||||
*
|
||||
* @param length The minimum length of segments. The length will
|
||||
* increase if the the segments hit the minimum angle. Default is 1.0.
|
||||
*/
|
||||
static void SetMinCircularEdgeLength(float length) {
|
||||
if (length <= 0) return;
|
||||
circularEdgeLength_ = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default number of circular segments for the
|
||||
* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and
|
||||
* Manifold::Revolve() constructors. Overrides the edge length and angle
|
||||
* constraints and sets the number of segments to exactly this value.
|
||||
*
|
||||
* @param number Number of circular segments. Default is 0, meaning no
|
||||
* constraint is applied.
|
||||
*/
|
||||
static void SetCircularSegments(int number) {
|
||||
if (number < 3 && number != 0) return;
|
||||
circularSegments_ = number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the result of the SetMinCircularAngle(),
|
||||
* SetMinCircularEdgeLength(), and SetCircularSegments() defaults.
|
||||
*
|
||||
* @param radius For a given radius of circle, determine how many default
|
||||
* segments there will be.
|
||||
*/
|
||||
static int GetCircularSegments(float radius) {
|
||||
if (circularSegments_ > 0) return circularSegments_;
|
||||
int nSegA = 360.0f / circularAngle_;
|
||||
int nSegL = 2.0f * radius * glm::pi<float>() / circularEdgeLength_;
|
||||
int nSeg = fmin(nSegA, nSegL) + 3;
|
||||
nSeg -= nSeg % 4;
|
||||
return nSeg;
|
||||
}
|
||||
///@}
|
||||
};
|
||||
|
||||
/** @defgroup Debug
|
||||
* @brief Debugging features
|
||||
*
|
||||
|
||||
+2
-2
@@ -18,14 +18,14 @@ add_subdirectory(third_party)
|
||||
|
||||
enable_testing()
|
||||
|
||||
set(SOURCE_FILES polygon_test.cpp manifold_test.cpp boolean_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp)
|
||||
set(SOURCE_FILES polygon_test.cpp manifold_test.cpp boolean_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp cross_section_test.cpp)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
list(APPEND SOURCE_FILES manifoldc_test.cpp)
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold sdf samples samplesGPU)
|
||||
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold sdf samples samplesGPU cross_section)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
target_link_libraries(${PROJECT_NAME} manifoldc)
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "public.h"
|
||||
#include "test.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshIO.h"
|
||||
#endif
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
TEST(CrossSection, MirrorUnion) {
|
||||
auto a = CrossSection::Square({5., 5.}, true);
|
||||
auto b = a.Translate({2.5, 2.5});
|
||||
auto cross = a + b + b.Mirror({-1, 1});
|
||||
auto result = Manifold::Extrude(cross, 5.);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels)
|
||||
ExportMesh("cross_section_mirror_union.glb", result.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
auto area_a = a.Area();
|
||||
EXPECT_EQ(area_a + 1.5 * area_a, cross.Area());
|
||||
EXPECT_TRUE(a.Mirror(glm::vec2(0)).IsEmpty());
|
||||
}
|
||||
|
||||
TEST(CrossSection, RoundOffset) {
|
||||
auto a = CrossSection::Square({20., 20.}, true);
|
||||
auto rounded = a.Offset(5., CrossSection::JoinType::Round);
|
||||
auto result = Manifold::Extrude(rounded, 5.);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels)
|
||||
ExportMesh("cross_section_round_offset.glb", result.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
EXPECT_TRUE(result.IsManifold());
|
||||
}
|
||||
|
||||
TEST(CrossSection, Empty) {
|
||||
Polygons polys(2);
|
||||
auto e = CrossSection(polys);
|
||||
EXPECT_TRUE(e.IsEmpty());
|
||||
}
|
||||
|
||||
TEST(CrossSection, RectClip) {
|
||||
auto sq = CrossSection::Square({10, 10});
|
||||
auto rect = Rect({0, 0}, {10, 5});
|
||||
auto clipped = sq.RectClip(rect);
|
||||
|
||||
EXPECT_EQ(sq.Area() / 2, clipped.Area());
|
||||
EXPECT_TRUE(rect.Contains({5, 5}));
|
||||
EXPECT_TRUE(rect.Contains(Rect()));
|
||||
EXPECT_TRUE(rect.DoesOverlap(Rect({5, 5}, {15, 15})));
|
||||
EXPECT_TRUE(Rect().IsEmpty());
|
||||
}
|
||||
|
||||
TEST(CrossSection, Transform) {
|
||||
auto sq = CrossSection::Square({10., 10.});
|
||||
auto a = sq.Rotate(45).Scale({2, 3}).Translate({4, 5});
|
||||
|
||||
glm::mat3x3 trans(1.0f, 0.0f, 0.0f, //
|
||||
0.0f, 1.0f, 0.0f, //
|
||||
4.0f, 5.0f, 1.0f);
|
||||
glm::mat3x3 rot(cosd(45), sind(45), 0.0f, //
|
||||
-sind(45), cosd(45), 0.0f, //
|
||||
0.0f, 0.0f, 1.0f);
|
||||
glm::mat3x3 scale(2.0f, 0.0f, 0.0f, //
|
||||
0.0f, 3.0f, 0.0f, //
|
||||
0.0f, 0.0f, 1.0f);
|
||||
|
||||
auto b = sq.Transform(trans * scale * rot);
|
||||
|
||||
Identical(Manifold::Extrude(a, 1.).GetMesh(),
|
||||
Manifold::Extrude(b, 1.).GetMesh());
|
||||
}
|
||||
Reference in New Issue
Block a user