diff --git a/conandata.yml b/conandata.yml index e20c02a..d6a1e88 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1 +1 @@ -version: "1.0.1" +version: "1.1.0" diff --git a/include/Triangle3F.h b/include/Triangle3F.h index 68c8865..a247683 100644 --- a/include/Triangle3F.h +++ b/include/Triangle3F.h @@ -25,7 +25,7 @@ class Triangle3F return p3_; } - [[nodiscard]] Vector3F normal() const; + [[nodiscard]] std::optional normal() const; private: Point3F p1_; diff --git a/include/project.h b/include/project.h index 6a936bb..db9b9ec 100644 --- a/include/project.h +++ b/include/project.h @@ -45,4 +45,14 @@ std::vector doProject( const uint32_t viewport_width, const uint32_t viewport_height, const Vector3F& camera_normal, - const uint32_t face_id); \ No newline at end of file + const uint32_t face_id); + +std::vector doGetConnectedFaces( + const std::span& mesh_vertices, + const std::span& mesh_indices, + const std::span& mesh_uv, + const std::span& mesh_faces_connectivity, + const uint32_t texture_width, + const uint32_t texture_height, + const uint32_t face_id, + const double threshold_angle); \ No newline at end of file diff --git a/pyUvula/pyUvula.cpp b/pyUvula/pyUvula.cpp index 07c6dcd..b3dd25e 100644 --- a/pyUvula/pyUvula.cpp +++ b/pyUvula/pyUvula.cpp @@ -49,6 +49,24 @@ py::tuple pyUnwrap(const py::array_t& vertices_array, const py::array_t::format(), shape.size(), shape, strides)), texture_width, texture_height); } +py::list makePyPolygonsList(const std::vector& polygons) +{ + py::list py_result; + for (const Polygon& polygon : polygons) + { + py_result.append( + py::array_t(py::buffer_info( + const_cast(polygon.data()), + sizeof(float), + py::format_descriptor::format(), + 2, + { polygon.size(), static_cast(2) }, + { sizeof(float) * 2, sizeof(float) }))); + } + + return py_result; +} + py::list pyProject( const py::array_t& stroke_polygon_array, const py::array_t& mesh_vertices_array, @@ -86,7 +104,7 @@ py::list pyProject( const float* camera_normal_ptr = static_cast(camera_normal_buf.ptr); const Vector3F camera_normal(camera_normal_ptr[0], camera_normal_ptr[1], camera_normal_ptr[2]); - std::vector result = doProject( + const std::vector result = doProject( stroke_polygon, mesh_vertices, mesh_indices, @@ -101,20 +119,34 @@ py::list pyProject( camera_normal, face_id); - py::list py_result; - for (Polygon& polygon : result) - { - py_result.append( - py::array_t(py::buffer_info( - polygon.data(), - sizeof(float), - py::format_descriptor::format(), - 2, - { polygon.size(), static_cast(2) }, - { sizeof(float) * 2, sizeof(float) }))); - } + return makePyPolygonsList(result); +} - return py_result; +py::list pyGetConnectedFaces( + const py::array_t& mesh_vertices_array, + const py::array_t& mesh_indices_array, + const py::array_t& mesh_uv_array, + const py::array_t& mesh_faces_connectivity_array, + const uint32_t texture_width, + const uint32_t texture_height, + const uint32_t face_id, + const double threshold_angle) +{ + pybind11::buffer_info mesh_vertices_buffer = mesh_vertices_array.request(); + const std::span mesh_vertices = std::span(static_cast(mesh_vertices_buffer.ptr), mesh_vertices_buffer.shape[0]); + + pybind11::buffer_info mesh_indices_buffer = mesh_indices_array.request(); + const std::span mesh_indices = std::span(static_cast(mesh_indices_buffer.ptr), mesh_indices_buffer.shape[0]); + + pybind11::buffer_info mesh_uv_buffer = mesh_uv_array.request(); + const std::span mesh_uv = std::span(static_cast(mesh_uv_buffer.ptr), mesh_uv_buffer.shape[0]); + + pybind11::buffer_info mesh_faces_connectivity_buffer = mesh_faces_connectivity_array.request(); + const std::span mesh_faces_connectivity = std::span(static_cast(mesh_faces_connectivity_buffer.ptr), mesh_faces_connectivity_buffer.shape[0]); + + const std::vector result = doGetConnectedFaces(mesh_vertices, mesh_indices, mesh_uv, mesh_faces_connectivity, texture_width, texture_height, face_id, threshold_angle); + + return makePyPolygonsList(result); } PYBIND11_MODULE(pyUvula, module) @@ -124,4 +156,5 @@ PYBIND11_MODULE(pyUvula, module) module.def("unwrap", &pyUnwrap, "Given the vertices, indices of a mesh, unwrap UV for texture-coordinates."); module.def("project", &pyProject, "Projects a stroke polygon into an object texture."); + module.def("getConnectedFaces", &pyGetConnectedFaces, "Gets the polygons of the faces connected to the given initial face."); } diff --git a/src/Triangle3F.cpp b/src/Triangle3F.cpp index 12e7aa2..c153313 100644 --- a/src/Triangle3F.cpp +++ b/src/Triangle3F.cpp @@ -8,7 +8,7 @@ Triangle3F::Triangle3F(const Point3F& p1, const Point3F& p2, const Point3F& p3) { } -Vector3F Triangle3F::normal() const +std::optional Triangle3F::normal() const { - return Vector3F(p1_, p2_).cross(Vector3F(p1_, p3_)); + return Vector3F(p1_, p2_).cross(Vector3F(p1_, p3_)).normalized(); } \ No newline at end of file diff --git a/src/project.cpp b/src/project.cpp index 195b0dc..de3d1a2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -155,6 +155,19 @@ std::vector toPolygons(const ClipperLib::Paths& paths) return result; } +std::vector unionPolygons(const std::vector& polygons) +{ + ClipperLib::Paths uv_areas_path; + uv_areas_path.reserve(polygons.size()); + for (const Polygon& polygon : polygons) + { + uv_areas_path.push_back(toPath(polygon)); + } + uv_areas_path = unionPaths(uv_areas_path); + + return toPolygons(uv_areas_path); +} + std::vector doProject( const std::span& stroke_polygon, const std::span& mesh_vertices, @@ -186,9 +199,9 @@ std::vector doProject( const Face face = getFace(mesh_indices, candidate_face_id); const Triangle3F face_triangle = getFaceTriangle(mesh_vertices, face); - const Vector3F face_normal = face_triangle.normal(); + const std::optional face_normal = face_triangle.normal(); - if (face_normal.dot(camera_normal) < 0) + if (! face_normal.has_value() || face_normal->dot(camera_normal) < 0) { // Facing away from the viewer continue; @@ -233,13 +246,80 @@ std::vector doProject( } } - ClipperLib::Paths uv_areas_path; - uv_areas_path.reserve(result.size()); - for (const Polygon& polygon : result) + return unionPolygons(result); +} + +std::vector doGetConnectedFaces( + const std::span& mesh_vertices, + const std::span& mesh_indices, + const std::span& mesh_uv, + const std::span& mesh_faces_connectivity, + const uint32_t texture_width, + const uint32_t texture_height, + const uint32_t face_id, + const double threshold_angle) +{ + constexpr double threshold_angle_limit = 0.02; + const double actual_threshold_angle = std::cos(std::max(threshold_angle, threshold_angle_limit)); + + const Face initial_face = getFace(mesh_indices, face_id); + const Triangle3F initial_triangle = getFaceTriangle(mesh_vertices, initial_face); + const std::optional initial_face_normal = initial_triangle.normal(); + + if (! initial_face_normal.has_value()) { - uv_areas_path.push_back(toPath(polygon)); + return {}; } - uv_areas_path = unionPaths(uv_areas_path); - return toPolygons(uv_areas_path); + std::vector result; + std::unordered_set visited_faces; + std::queue faces_to_visit; + faces_to_visit.push(face_id); + while (! faces_to_visit.empty()) + { + const uint32_t current_face_id = faces_to_visit.front(); + faces_to_visit.pop(); + + if (visited_faces.contains(current_face_id)) + { + continue; + } + + visited_faces.insert(current_face_id); + + const Face current_face = getFace(mesh_indices, current_face_id); + const Triangle3F current_triangle = getFaceTriangle(mesh_vertices, current_face); + const std::optional current_face_normal = current_triangle.normal(); + + if (! current_face_normal.has_value()) + { + continue; + } + + const double angle_cosine = std::abs(initial_face_normal->dot(*current_face_normal)); + if (angle_cosine >= actual_threshold_angle) + { + // Add the polygon to the result + const Triangle2F current_uv = getFaceUv(mesh_uv, current_face); + Polygon current_polygon; + for (const Point2F& point_uv : { current_uv.p1, current_uv.p2, current_uv.p3 }) + { + current_polygon.push_back(Point2F(point_uv.x * texture_width, point_uv.y * texture_height)); + } + result.push_back(std::move(current_polygon)); + + // Visit the connected faces + const FaceSigned& connected_faces = mesh_faces_connectivity[current_face_id]; + + for (int32_t connected_face_id : { connected_faces.i1, connected_faces.i2, connected_faces.i3 }) + { + if (connected_face_id >= 0 && ! visited_faces.contains(connected_face_id)) + { + faces_to_visit.push(connected_face_id); + } + } + } + } + + return unionPolygons(result); } \ No newline at end of file