From 27e640180fefc7f814124171229fc5fbdf7d8e91 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 31 Aug 2022 08:35:43 +0200 Subject: [PATCH] Add unprojecting of SurfacePatch contours --- src/libslic3r/CutSurface.cpp | 406 ++++++++++++++++++++++++++++++----- src/libslic3r/Emboss.cpp | 9 + src/libslic3r/Emboss.hpp | 10 +- 3 files changed, 366 insertions(+), 59 deletions(-) diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp index dea1a22dd..74ebfe094 100644 --- a/src/libslic3r/CutSurface.cpp +++ b/src/libslic3r/CutSurface.cpp @@ -288,17 +288,31 @@ CutAOIs cut_from_model(CutMesh &cgal_model, float projection_ratio, const ShapePoint2index &s2i); +using Loop = std::vector; +using Loops = std::vector; + +/// +/// Create closed loops of contour vertices created from open half edges +/// +/// Unsorted half edges +/// Source mesh for half edges +/// Closed loops +Loops create_loops(const std::vector &outlines, const CutMesh &mesh); + // To track during diff_models, // what was cutted off, from CutAOI struct SurfacePatch { // converted cut to CGAL mesh + // Mesh is reduced. + // (do not contain divided triangles on contour - created by side Quad) CutMesh mesh; // CvtVI2VI cvt = mesh.property_map(patch_source_name); // Conversion VI from this patch to source VI(model) is stored in mesh property - - // converted source.second to mesh half edges - std::vector outline; + + // Outlines - converted CutAOI.second (half edges) + // to loops (vertex indicies) by function create_loops + Loops loops; // bounding box of mesh BoundingBoxf3 bb; @@ -311,11 +325,8 @@ struct SurfacePatch // index of shape from ExPolygons size_t shape_id = 0; - //// Used only during clipping phase - // flag that part will be deleted - bool full_inside = false; - // flag that Patch could contain more than one part - bool just_cliped = false; + // flag that this patch contain whole CutAOI + bool is_whole_aoi = true; }; using SurfacePatches = std::vector; @@ -362,6 +373,30 @@ SurfacePatches diff_models(VCutAOIs &cuts, /*const*/ CutMeshes &models, const Project3f &projection); +/// +/// Checking whether patch is uninterrupted cover of whole expolygon it belongs. +/// +/// Part of surface to check +/// Source shape +/// Source of cut +/// True when cover whole expolygon otherwise false +bool is_over_whole_expoly(const CutAOI &cutAOI, + const ExPolygon &shape, + const CutMesh &mesh); + +/// +/// Checking whether patch is uninterrupted cover of whole expolygon it belongs. +/// +/// Part of surface to check +/// Source shape +/// True when cover whole expolygon otherwise false +bool is_over_whole_expoly(const SurfacePatch &patch, + const ExPolygons &shapes, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes); + +ExPolygon to_expoly(const SurfacePatch &patch, const Project &unproject); + /// /// To select surface near projection distance /// @@ -376,9 +411,6 @@ struct ProjectionDistance // index of Patch uint32_t patch_index = std::numeric_limits::max(); - // index of half edge in AOI - uint32_t hi_index = std::numeric_limits::max(); - // signed distance to projection float distance = std::numeric_limits::max(); }; @@ -405,6 +437,21 @@ VDistances calc_distances(const SurfacePatches &patches, size_t count_shapes_points, float projection_ratio); +/// +/// Fill area of expolygons by patches +/// Not used patches are discarded(removed from vector) +/// +/// In/Out parts of surface to be filtered +/// Depth distances for outline points +/// Shapes to be filled by patches +/// Starting point for select correct depth +/// Help struct to address point inside of shapes by one index +void filter_patches(SurfacePatches &patches, + const VDistances &distances, + const ExPolygons &shapes, + const Point ¢er, + const ShapePoint2index &s2i); + /// /// Select distances in similar depth between expolygons /// @@ -424,11 +471,9 @@ ProjectionDistances choose_best_distance( /// /// /// -/// /// Mask of used patch std::vector select_patches(const ProjectionDistances &best_distances, - const SurfacePatches &patches, - const VCutAOIs &cuts); + const SurfacePatches &patches); /// /// Merge masked patches to one surface cut @@ -463,6 +508,8 @@ void store(const Emboss::IProjection &projection, const Point &point_to_project, #endif // DEBUG_OUTPUT_DIR } // namespace privat +#include "libslic3r/SVG.hpp" + SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes, const std::vector &models, const Emboss::IProjection &projection, @@ -535,15 +582,37 @@ SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes, for (priv::SurfacePatch &patch : patches) patch.shape_id = s2i.calc_id(patch.shape_id).expolygons_index; + //// Only for create function + //std::vector is_filling; + //is_filling.reserve(patches.size()); + //for (priv::SurfacePatch &patch : patches) + // is_filling.push_back(priv::is_over_whole_expoly(patch, shapes, model_cuts, cgal_models)); + + //// convert patch to 2d + //ExPolygons fill; + //fill.reserve(patches.size()); + //for (priv::SurfacePatch &patch : patches) + // fill.push_back(to_expoly(patch, projection)); + + //{ + // SVG svg("C:/data/temp/filled_text.svg"); + // svg.draw(shapes, "gray"); + // svg.draw(fill, "green"); + //} + // calc distance to projection for all outline points of cutAOI(shape) // it is used for distiguish the top one uint32_t shapes_points = s2i.get_count(); // for each point collect all projection distances priv::VDistances distances = priv::calc_distances(patches, cgal_models, cgal_shape, shapes_points, projection_ratio); + //assert(shapes_bb.center().x() == 0 && shapes_bb.center().y() == 0); + //Point start = shapes_bb.center(); + //priv::filter_patches(patches, distances, shapes, start, s2i); + // for each point select best projection priv::ProjectionDistances best_projection = priv::choose_best_distance(distances, shapes, shapes_bb.center(), s2i); - std::vector use_patch = priv::select_patches(best_projection, patches, model_cuts); + std::vector use_patch = priv::select_patches(best_projection, patches); SurfaceCut result = merge_patches(patches, use_patch); #ifdef DEBUG_OUTPUT_DIR priv::store(result, DEBUG_OUTPUT_DIR + "result.obj", DEBUG_OUTPUT_DIR + "result_contours/"); @@ -1667,9 +1736,9 @@ priv::VDistances priv::calc_distances(const SurfacePatches &patches, uint32_t patch_index = &patch - &patches.front(); // map is created during patch creation / dividing const CvtVI2VI& cvt = patch.mesh.property_map(patch_source_name).first; - // for each half edge of outline - for (const HI& hi : patch.outline) { - VI vi_patch = patch.mesh.source(hi); + // for each point on outline + for (const Loop &loop : patch.loops) + for (const VI &vi_patch : loop) { VI vi_model = cvt[vi_patch]; if (!vi_model.is_valid()) continue; const IntersectingElement *ie = vert_shape_map[vi_model]; @@ -1681,10 +1750,10 @@ priv::VDistances priv::calc_distances(const SurfacePatches &patches, std::vector &pds = result[pi]; uint32_t model_index = patch.model_id; uint32_t aoi_index = patch.aoi_id; - uint32_t hi_index = &hi - &patch.outline.front(); + //uint32_t hi_index = &hi - &patch.outline.front(); const P3 &p = patch.mesh.point(vi_patch); float distance = calc_distance(p, pi, shapes_mesh, projection_ratio); - pds.push_back({model_index, aoi_index, patch_index, hi_index, distance}); + pds.push_back({model_index, aoi_index, patch_index, distance}); } } return result; @@ -2115,7 +2184,72 @@ priv::ClosePoint priv::find_close_point(const Point &p, return cp; } -// IMPROVE: create better structure to find closest points e.g. Tree +void priv::filter_patches(SurfacePatches &patches, + const VDistances &distances, + const ExPolygons &shapes, + const Point ¢er, + const ShapePoint2index &s2i) +{ + assert(distances.size() == count_points(shapes)); + // collect closest projection for source shape point(outline point) + ProjectionDistances closest(distances.size()); + + // store info about finished shapes + std::vector finished_shapes(shapes.size(), {false}); + + // wanted distance from ideal projection + // Distances are relative to projection distance + // so first wanted distance is the closest one (ZERO) + float wanted_distance = 0.f; + + // For each shape point flag wether exist some distance. + std::vector mask_distances(s2i.get_count(), {true}); + for (const auto &d : distances) + if (d.empty()) mask_distances[&d - &distances.front()] = false; + + // Select point from shapes(text contour) which is closest to center (all in 2d) + uint32_t unfinished_index = find_closest_point_index(center, shapes, s2i, mask_distances); + +#ifdef DEBUG_OUTPUT_DIR + std::vector> connections; + connections.reserve(shapes.size()); + connections.emplace_back(unfinished_index, unfinished_index); +#endif // DEBUG_OUTPUT_DIR + + do { + const ProjectionDistance* pd = get_closest_projection(distances[unfinished_index], wanted_distance); + // selection of closest_id should proove that pd has value + // (functions: get_closest_point_index and find_close_point_in_points) + assert(pd != nullptr); + fill_shape_distances(unfinished_index, *pd, closest, finished_shapes, s2i, shapes, distances); + + // The most close points between finished and unfinished shapes + auto [finished, unfinished] = find_closest_point_pair( + shapes, finished_shapes, s2i, mask_distances); + + // detection of end (best doesn't have value) + if (finished == std::numeric_limits::max()) break; + + assert(unfinished != std::numeric_limits::max()); + const ProjectionDistance &closest_pd = closest[finished]; + // check that best_cp is finished and has result + assert(closest_pd.aoi_index != std::numeric_limits::max()); + wanted_distance = closest_pd.distance; + unfinished_index = unfinished; + +#ifdef DEBUG_OUTPUT_DIR + connections.emplace_back(finished, unfinished); +#endif // DEBUG_OUTPUT_DIR + } while (true); //(unfinished_index != std::numeric_limits::max()); +#ifdef DEBUG_OUTPUT_DIR + store(shapes, mask_distances, connections, DEBUG_OUTPUT_DIR + "closest_points.svg"); +#endif // DEBUG_OUTPUT_DIR + + + + +} + // IMPROVE2: when select distance fill in all distances from Patch priv::ProjectionDistances priv::choose_best_distance( const VDistances &distances, @@ -2594,6 +2728,18 @@ priv::SurfacePatch priv::create_surface_patch(const std::vector &fis, // diff_models help functions namespace priv { +struct SurfacePatchEx +{ + SurfacePatch patch; + + // flag that part will be deleted + bool full_inside = false; + // flag that Patch could contain more than one part + bool just_cliped = false; +}; +using SurfacePatchesEx = std::vector; + + using BBS = std::vector; /// /// Create bounding boxes for AOI @@ -2670,7 +2816,7 @@ SurfacePatch separate_patch(const std::vector &fis, /// /// index into patches /// In/Out Patches -void divide_patch(size_t i, SurfacePatches &patches); +void divide_patch(size_t i, SurfacePatchesEx &patches); /// /// Fill outline in patches by open edges @@ -2791,11 +2937,13 @@ priv::SurfacePatch priv::separate_patch(const std::vector& fis, return patch_new; } -void priv::divide_patch(size_t i, SurfacePatches &patches) { - SurfacePatch &patch = patches[i]; - assert(patch.just_cliped); - patch.just_cliped = false; +void priv::divide_patch(size_t i, SurfacePatchesEx &patches) +{ + SurfacePatchEx &patch_ex = patches[i]; + assert(patch_ex.just_cliped); + patch_ex.just_cliped = false; + SurfacePatch& patch = patch_ex.patch; CutMesh& cm = patch.mesh; assert(!cm.faces().empty()); std::string patch_number_name = "f:patch_number"; @@ -2806,7 +2954,7 @@ void priv::divide_patch(size_t i, SurfacePatches &patches) { std::vector fis; fis.reserve(cm.faces().size()); - SurfacePatches new_patches; + SurfacePatchesEx new_patches; std::vector queue; // IMPROVE: create groups around triangles and than connect groups for (FI fi_cm : cm.faces()) { @@ -2816,8 +2964,10 @@ void priv::divide_patch(size_t i, SurfacePatches &patches) { if (!fis.empty()) { // Be carefull after push to patches, // all ref on patch contain non valid values - SurfacePatch patch_n = separate_patch(fis, patches[i], cvt_from); - new_patches.emplace_back(patch_n); + SurfacePatchEx patch_ex_n; + patch_ex_n.patch = separate_patch(fis, patch, cvt_from); + patch_ex_n.patch.is_whole_aoi = false; + new_patches.push_back(std::move(patch_ex_n)); fis.clear(); } // flood fill from triangle fi_cm to surrounding @@ -2841,7 +2991,7 @@ void priv::divide_patch(size_t i, SurfacePatches &patches) { // speed up for only one patch - no dividing (the most common) if (new_patches.empty()) { patch.bb = bounding_box(cm); - return; + patch.is_whole_aoi = false; } else { patch = separate_patch(fis, patch, cvt_from); patches.insert(patches.end(), new_patches.begin(), new_patches.end()); @@ -2849,8 +2999,9 @@ void priv::divide_patch(size_t i, SurfacePatches &patches) { } void priv::collect_open_edges(SurfacePatches &patches) { + std::vector open_half_edges; for (SurfacePatch &patch : patches) { - patch.outline.clear(); + open_half_edges.clear(); const CutMesh &mesh = patch.mesh; for (FI fi : mesh.faces()) { HI hi1 = mesh.halfedge(fi); @@ -2864,9 +3015,11 @@ void priv::collect_open_edges(SurfacePatches &patches) { for (HI hi : {hi1, hi2, hi3}) { HI hi_op = mesh.opposite(hi); FI fi_op = mesh.face(hi_op); - if (!fi_op.is_valid()) patch.outline.push_back(hi); + if (!fi_op.is_valid()) + open_half_edges.push_back(hi); } } + patch.loops = create_loops(open_half_edges, mesh); } } @@ -2886,8 +3039,11 @@ priv::SurfacePatches priv::diff_models(VCutAOIs &cuts, Trees trees(models.size()); SurfacePatches patches; + // queue of patches for one AOI (permanent with respect to for loop) - SurfacePatches aoi_patches; + SurfacePatchesEx aoi_patches; + + //SurfacePatches aoi_patches; patches.reserve(m2i.get_count()); // only approximation of count size_t index = 0; for (size_t model_index = 0; model_index < models.size(); ++model_index) { @@ -2899,21 +3055,25 @@ priv::SurfacePatches priv::diff_models(VCutAOIs &cuts, for (size_t cut_index = 0; cut_index < model_cuts.size(); ++cut_index, ++index) { const CutAOI &cut = model_cuts[cut_index]; - SurfacePatch patch = create_surface_patch(cut.first, cut_model_, &vertex_reduction_map); + SurfacePatchEx patch_ex; + SurfacePatch &patch = patch_ex.patch; + patch = create_surface_patch(cut.first, cut_model_, &vertex_reduction_map); patch.bb = bbs[index]; patch.aoi_id = cut_index; patch.model_id = model_index; patch.shape_id = get_shape_point_index(cut, cut_model); + patch.is_whole_aoi = true; aoi_patches.clear(); - aoi_patches.push_back(patch); + aoi_patches.push_back(patch_ex); for (size_t model_index2 = 0; model_index2 < models.size(); ++model_index2) { // do not clip source model itself if (model_index == model_index2) continue; - for (SurfacePatch &patch : aoi_patches) { + for (SurfacePatchEx &patch_ex : aoi_patches) { + SurfacePatch &patch = patch_ex.patch; if (has_bb_intersection(patch.bb, model_index2, bbs, m2i) && clip_cut(patch, models[model_index2])){ - patch.just_cliped = true; + patch_ex.just_cliped = true; } else { // build tree on demand // NOTE: it is possible not neccessary: e.g. one model @@ -2925,7 +3085,7 @@ priv::SurfacePatches priv::diff_models(VCutAOIs &cuts, tree.build(); } if (is_patch_inside_of_model(patch, tree, projection)) - patch.full_inside = true; + patch_ex.full_inside = true; } } // erase full inside @@ -2943,10 +3103,13 @@ priv::SurfacePatches priv::diff_models(VCutAOIs &cuts, if (aoi_patches[i].just_cliped) divide_patch(i, aoi_patches); } - if (!aoi_patches.empty()) - patches.insert(patches.end(), - aoi_patches.begin(), - aoi_patches.end()); + + if (!aoi_patches.empty()) { + patches.reserve(patches.size() + aoi_patches.size()); + for (SurfacePatchEx &patch : aoi_patches) + patches.push_back(std::move(patch.patch)); + + } } cut_model_.remove_property_map(vertex_reduction_map); } @@ -2957,10 +3120,104 @@ priv::SurfacePatches priv::diff_models(VCutAOIs &cuts, return patches; } +bool priv::is_over_whole_expoly(const SurfacePatch &patch, + const ExPolygons &shapes, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes) +{ + if (!patch.is_whole_aoi) return false; + return is_over_whole_expoly(cutAOIs[patch.model_id][patch.aoi_id], + shapes[patch.shape_id], + meshes[patch.model_id]); +} + +bool priv::is_over_whole_expoly(const CutAOI &cutAOI, + const ExPolygon &shape, + const CutMesh &mesh) +{ + // NonInterupted contour is without other point and contain all from shape + const VertexShapeMap &vert_shape_map = mesh.property_map(vert_shape_map_name).first; + for (HI hi : cutAOI.second) { + const IntersectingElement *ie_s = vert_shape_map[mesh.source(hi)]; + const IntersectingElement *ie_t = vert_shape_map[mesh.target(hi)]; + if (ie_s == nullptr || ie_t == nullptr) + return false; + + assert(ie_s->attr != (unsigned char) IntersectingElement::Type::undefined); + assert(ie_t->attr != (unsigned char) IntersectingElement::Type::undefined); + + // check if it is neighbor indices + uint32_t i_s = ie_s->shape_point_index; + uint32_t i_t = ie_t->shape_point_index; + assert(i_s != std::numeric_limits::max()); + assert(i_t != std::numeric_limits::max()); + if (i_s == std::numeric_limits::max() || + i_t == std::numeric_limits::max()) + return false; + + // made by same index + if (i_s == i_t) continue; + + // order from source to target + if (i_s > i_t) { + std::swap(i_s, i_t); + std::swap(ie_s, ie_t); + } + // Must be after fix order !! + bool is_last_polygon_segment = ie_s->is_first() && ie_t->is_last(); + if (is_last_polygon_segment) { + std::swap(i_s, i_t); + std::swap(ie_s, ie_t); + } + + // Is continous indices + if (!is_last_polygon_segment && + (ie_s->is_last() || (i_s + 1) != i_t)) + return false; + + IntersectingElement::Type t_s = ie_s->get_type(); + IntersectingElement::Type t_t = ie_t->get_type(); + if (t_s == IntersectingElement::Type::undefined || + t_t == IntersectingElement::Type::undefined) + return false; + + // next segment must start with edge intersection + if (t_t != IntersectingElement::Type::edge_1) + return false; + + // After face1 must be edge2 or face2 + if (t_s == IntersectingElement::Type::face_1) + return false; + } + + // When all open edges are on contour than there is NO holes is shape + auto is_open = [&mesh](HI hi)->bool { + HI opposite = mesh.opposite(hi); + return !mesh.face(opposite).is_valid(); + }; + + std::vector opens; // copy + opens.reserve(cutAOI.second.size()); + for (HI hi : cutAOI.second) // from lower to bigger + if (is_open(hi)) opens.push_back(hi); + std::sort(opens.begin(), opens.end()); + + for (FI fi: cutAOI.first) { + HI face_hi = mesh.halfedge(fi); + for (HI hi : mesh.halfedges_around_face(face_hi)) { + if (!is_open(hi)) continue; + // open edge + auto lb = std::lower_bound(opens.begin(), opens.end(), hi); + if (lb == opens.end() || *lb != hi) + return false; // not in contour + } + } + return true; +} + std::vector priv::select_patches( const ProjectionDistances &best_distances, - const SurfacePatches &patches, - const VCutAOIs &cuts) + const SurfacePatches &patches) { std::vector in_distances(patches.size(), {false}); for (const ProjectionDistance &d : best_distances) { @@ -3021,17 +3278,6 @@ std::vector priv::select_patches( // help function to 'merge_patches' namespace priv { - -using Loop = std::vector; -using Loops = std::vector; -/// -/// Create closed loops of contour vertices created from half edges -/// -/// Unsorted half edges -/// Source mesh for half edges -/// Closed loops -Loops create_loops(const std::vector &outlines, const CutMesh& mesh); - /// /// Convert patch to indexed_triangle_set /// @@ -3103,6 +3349,51 @@ priv::Loops priv::create_loops(const std::vector &outlines, const CutMesh& m return loops; } +#include "ClipperUtils.hpp" +ExPolygon priv::to_expoly(const SurfacePatch &patch, const Project &project) +{ + assert(!patch.loops.empty()); + if (patch.loops.empty()) return {}; + + // NOTE: this method is working only when patch did not contain outward faces + Polygons polys; + polys.reserve(patch.loops.size()); + // project conture into 2d space to fillconvert outlines to + + Points pts; + for (const Loop &l : patch.loops) { + pts.clear(); + pts.reserve(l.size()); + for (VI vi : l) { + const P3 &p3 = patch.mesh.point(vi); + Vec3f p(p3.x(), p3.y(), p3.z()); + std::optional p2_opt = project.unproject(p); + + // Check when appear that skip is enough for poit which can't be unprojected + // - it could break contour + assert(p2_opt.has_value()); + if (!p2_opt.has_value()) continue; + + pts.push_back(*p2_opt); + } + // minimal is triangle + assert(pts.size() >= 3); + if (pts.size() < 3) continue; + + polys.emplace_back(pts); + } + + assert(!polys.empty()); + if (polys.empty()) return {}; + + ExPolygons expolys = Slic3r::union_ex(polys, ClipperLib::PolyFillType::pftEvenOdd); + + assert(expolys.size() == 1); + if (expolys.empty()) return {}; + + return expolys.front(); +} + SurfaceCut priv::patch2cut(SurfacePatch &patch) { CutMesh &mesh = patch.mesh; @@ -3144,9 +3435,8 @@ SurfaceCut priv::patch2cut(SurfacePatch &patch) sc.indices.push_back(ti); } - Loops loops = create_loops(patch.outline, patch.mesh); - sc.contours.reserve(loops.size()); - for (const Loop &loop : loops) { + sc.contours.reserve(patch.loops.size()); + for (const Loop &loop : patch.loops) { sc.contours.push_back({}); std::vector &contour = sc.contours.back(); contour.reserve(loop.size()); diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index d1548cd19..ebd487c2e 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -969,6 +969,10 @@ Vec3f Emboss::ProjectZ::project(const Vec3f &point) const return res; } +std::optional Emboss::ProjectZ::unproject(const Vec3f &p) const { + return Point(p.x() / SHAPE_SCALE, p.y() / SHAPE_SCALE); +} + Transform3d Emboss::create_transformation_onto_surface(const Vec3f &position, const Vec3f &normal, float up_limit) @@ -1041,3 +1045,8 @@ Vec3f Emboss::OrthoProject::project(const Vec3f &point) const { return point + m_direction; } + +std::optional Emboss::OrthoProject::unproject(const Vec3f &p) const { + Vec3d pp = m_matrix_inv * p.cast(); + return Point(pp.x(), pp.y()); +} \ No newline at end of file diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 13d19f06c..e11dfb7a7 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -232,6 +232,8 @@ public: /// second - back spatial point /// virtual std::pair create_front_back(const Point &p) const = 0; + + virtual std::optional unproject(const Vec3f &p) const = 0; }; /// @@ -259,6 +261,7 @@ public: // Inherited via IProject std::pair create_front_back(const Point &p) const override; Vec3f project(const Vec3f &point) const override; + std::optional unproject(const Vec3f &p) const override; float m_depth; }; @@ -280,6 +283,9 @@ public: Vec3f project(const Vec3f &point) const override{ return core->project(point); } + std::optional unproject(const Vec3f &p) const override { + return core->unproject(p / m_scale); + } }; class OrthoProject3f : public Emboss::IProject3f @@ -295,13 +301,15 @@ public: Transform3d m_matrix; // size and direction of emboss for ortho projection Vec3f m_direction; + Transform3d m_matrix_inv; public: OrthoProject(Transform3d matrix, Vec3f direction) - : m_matrix(matrix), m_direction(direction) + : m_matrix(matrix), m_direction(direction), m_matrix_inv(matrix.inverse()) {} // Inherited via IProject std::pair create_front_back(const Point &p) const override; Vec3f project(const Vec3f &point) const override; + std::optional unproject(const Vec3f &p) const override; }; };