Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy transformations for CrossSection #355

Merged
merged 6 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/cross_section/include/cross_section.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

#include <clipper2/clipper.h>

#include <memory>

#include "clipper2/clipper.core.h"
#include "clipper2/clipper.offset.h"
#include "glm/ext/matrix_float3x2.hpp"
#include "glm/ext/vector_float2.hpp"
#include "public.h"

Expand Down Expand Up @@ -96,8 +99,10 @@ class CrossSection {
///@}

private:
C2::PathsD paths_;
mutable std::shared_ptr<const C2::PathsD> paths_;
mutable glm::mat3x2 transform_ = glm::mat3x2(1.0f);
CrossSection(C2::PathsD paths);
C2::PathsD GetPaths() const;
};
/** @} */

Expand Down
144 changes: 75 additions & 69 deletions src/cross_section/src/cross_section.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

#include <clipper2/clipper.h>

#include <memory>
#include <vector>

#include "clipper2/clipper.core.h"
#include "clipper2/clipper.engine.h"
#include "clipper2/clipper.offset.h"
#include "glm/ext/matrix_float3x2.hpp"
#include "glm/ext/vector_float2.hpp"
#include "glm/geometric.hpp"
#include "glm/glm.hpp"
Expand Down Expand Up @@ -91,21 +93,42 @@ C2::PathD pathd_of_contour(const SimplePolygon& ctr) {
}
return p;
}

C2::PathsD transform(const C2::PathsD ps, const glm::mat3x2 m) {
const bool invert = glm::determinant(glm::mat2(m)) < 0;
auto transformed = C2::PathsD();
transformed.reserve(ps.size());
for (auto path : ps) {
auto sz = path.size();
auto s = C2::PathD(sz);
for (int i = 0; i < sz; ++i) {
auto idx = invert ? sz - 1 - i : i;
s[idx] = v2_to_pd(m * glm::vec3(path[i].x, path[i].y, 1));
}
transformed.push_back(s);
}
return transformed;
}

std::shared_ptr<const C2::PathsD> shared_paths(const C2::PathsD& ps) {
return std::make_shared<const C2::PathsD>(ps);
}
} // namespace

namespace manifold {
CrossSection::CrossSection() { paths_ = C2::PathsD(); }
CrossSection::CrossSection() { paths_ = shared_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_);
paths_ = other.paths_;
transform_ = other.transform_;
}
CrossSection::CrossSection(C2::PathsD ps) { paths_ = ps; }
CrossSection::CrossSection(C2::PathsD ps) { paths_ = shared_paths(ps); }

CrossSection::CrossSection(const SimplePolygon& contour, FillRule fillrule) {
auto ps = C2::PathsD{(pathd_of_contour(contour))};
paths_ = C2::Union(ps, fr(fillrule), precision_);
paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));
}

CrossSection::CrossSection(const Polygons& contours, FillRule fillrule) {
Expand All @@ -114,7 +137,18 @@ CrossSection::CrossSection(const Polygons& contours, FillRule fillrule) {
for (auto ctr : contours) {
ps.push_back(pathd_of_contour(ctr));
}
paths_ = C2::Union(ps, fr(fillrule), precision_);
paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));
}

// All access to paths_ should be done through the GetPaths() method, which
// applies the accumulated transform_
C2::PathsD CrossSection::GetPaths() const {
if (transform_ == glm::mat3x2(1.0f)) {
return *paths_;
}
paths_ = shared_paths(transform(*paths_, transform_));
transform_ = glm::mat3x2(1.0f);
return *paths_;
}

CrossSection CrossSection::Square(const glm::vec2 dims, bool center) {
Expand Down Expand Up @@ -151,8 +185,8 @@ CrossSection CrossSection::Circle(float radius, int circularSegments) {
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_);
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, GetPaths(),
second.GetPaths(), precision_);
return CrossSection(res);
}

Expand All @@ -163,15 +197,15 @@ CrossSection CrossSection::BatchBoolean(
else if (crossSections.size() == 1)
return crossSections[0];

auto subjs = crossSections[0].paths_;
auto subjs = crossSections[0].GetPaths();
int n_clips = 0;
for (int i = 1; i < crossSections.size(); ++i) {
n_clips += crossSections[i].paths_.size();
n_clips += crossSections[i].GetPaths().size();
}
auto clips = C2::PathsD();
clips.reserve(n_clips);
for (int i = 1; i < crossSections.size(); ++i) {
auto ps = crossSections[i].paths_;
auto ps = crossSections[i].GetPaths();
clips.insert(clips.end(), ps.begin(), ps.end());
}

Expand Down Expand Up @@ -228,105 +262,77 @@ CrossSection& CrossSection::operator^=(const CrossSection& Q) {

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_);
auto ps = C2::RectClip(r, GetPaths(), 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);
glm::mat3x2 m(1.0f, 0.0f, //
0.0f, 1.0f, //
v.x, v.y);
return Transform(m);
}

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);
glm::mat3x2 m(c, s, //
-s, c, //
0.0f, 0.0f);
return Transform(m);
}

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);
glm::mat3x2 m(scale.x, 0.0f, //
0.0f, scale.y, //
0.0f, 0.0f);
return Transform(m);
}

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);
auto n = glm::normalize(glm::abs(ax));
auto m = glm::mat3x2(glm::mat2(1.0f) - 2.0f * glm::outerProduct(n, n));
return Transform(m);
}

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);
auto transformed = CrossSection();
transformed.transform_ = m * glm::mat3(transform_);
transformed.paths_ = paths_;
return transformed;
}

CrossSection CrossSection::Simplify(double epsilon) const {
auto ps = SimplifyPaths(paths_, epsilon, false);
auto ps = SimplifyPaths(GetPaths(), 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);
auto ps =
C2::InflatePaths(GetPaths(), delta, jt(jointype), C2::EndType::Polygon,
miter_limit, precision_, arc_tolerance);
return CrossSection(ps);
}

double CrossSection::Area() const { return C2::Area(paths_); }
double CrossSection::Area() const { return C2::Area(GetPaths()); }
Rect CrossSection::Bounds() const {
auto r = C2::GetBounds(paths_);
auto r = C2::GetBounds(GetPaths());
return Rect({r.left, r.bottom}, {r.right, r.top});
}
bool CrossSection::IsEmpty() const { return paths_.empty(); }
bool CrossSection::IsEmpty() const { return GetPaths().empty(); }

Polygons CrossSection::ToPolygons() const {
auto polys = Polygons();
polys.reserve(paths_.size());
for (auto p : paths_) {
auto paths = GetPaths();
polys.reserve(paths.size());
for (auto p : paths) {
auto sp = SimplePolygon();
sp.reserve(paths_.size());
sp.reserve(p.size());
for (auto v : p) {
sp.push_back({v.x, v.y});
}
Expand Down
14 changes: 9 additions & 5 deletions test/cross_section_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include <gtest/gtest.h>

#include "glm/geometric.hpp"
#include "manifold.h"
#include "polygon.h"
#include "public.h"
Expand All @@ -30,16 +31,15 @@ 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 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_FLOAT_EQ(2.5 * a.Area(), cross.Area());
EXPECT_TRUE(a.Mirror(glm::vec2(0)).IsEmpty());
}

Expand Down Expand Up @@ -89,7 +89,11 @@ TEST(CrossSection, Transform) {
0.0f, 0.0f, 1.0f);

auto b = sq.Transform(trans * scale * rot);
auto b_copy = CrossSection(b);

Identical(Manifold::Extrude(a, 1.).GetMesh(),
Manifold::Extrude(b, 1.).GetMesh());
auto ex_b = Manifold::Extrude(b, 1.).GetMesh();
Identical(Manifold::Extrude(a, 1.).GetMesh(), ex_b);

// same transformations are applied in b_copy (giving same result)
Identical(ex_b, Manifold::Extrude(b_copy, 1.).GetMesh());
}