Skip to content
Open
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
2 changes: 1 addition & 1 deletion conandata.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version: "1.0.1"
version: "1.1.0"
2 changes: 1 addition & 1 deletion include/Triangle3F.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Triangle3F
return p3_;
}

[[nodiscard]] Vector3F normal() const;
[[nodiscard]] std::optional<Vector3F> normal() const;

private:
Point3F p1_;
Expand Down
12 changes: 11 additions & 1 deletion include/project.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,14 @@ std::vector<Polygon> doProject(
const uint32_t viewport_width,
const uint32_t viewport_height,
const Vector3F& camera_normal,
const uint32_t face_id);
const uint32_t face_id);

std::vector<Polygon> doGetConnectedFaces(
const std::span<const Point3F>& mesh_vertices,
const std::span<const Face>& mesh_indices,
const std::span<const Point2F>& mesh_uv,
const std::span<const FaceSigned>& mesh_faces_connectivity,
const uint32_t texture_width,
const uint32_t texture_height,
const uint32_t face_id,
const double threshold_angle);
61 changes: 47 additions & 14 deletions pyUvula/pyUvula.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ py::tuple pyUnwrap(const py::array_t<float>& vertices_array, const py::array_t<i
return py::make_tuple(py::array(py::buffer_info(res.data(), strides[1], py::format_descriptor<float>::format(), shape.size(), shape, strides)), texture_width, texture_height);
}

py::list makePyPolygonsList(const std::vector<Polygon>& polygons)
{
py::list py_result;
for (const Polygon& polygon : polygons)
{
py_result.append(
py::array_t<float>(py::buffer_info(
const_cast<Point2F*>(polygon.data()),
sizeof(float),
py::format_descriptor<float>::format(),
2,
{ polygon.size(), static_cast<size_t>(2) },
{ sizeof(float) * 2, sizeof(float) })));
}

return py_result;
}

py::list pyProject(
const py::array_t<float>& stroke_polygon_array,
const py::array_t<float>& mesh_vertices_array,
Expand Down Expand Up @@ -86,7 +104,7 @@ py::list pyProject(
const float* camera_normal_ptr = static_cast<float*>(camera_normal_buf.ptr);
const Vector3F camera_normal(camera_normal_ptr[0], camera_normal_ptr[1], camera_normal_ptr[2]);

std::vector<Polygon> result = doProject(
const std::vector<Polygon> result = doProject(
stroke_polygon,
mesh_vertices,
mesh_indices,
Expand All @@ -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<float>(py::buffer_info(
polygon.data(),
sizeof(float),
py::format_descriptor<float>::format(),
2,
{ polygon.size(), static_cast<size_t>(2) },
{ sizeof(float) * 2, sizeof(float) })));
}
return makePyPolygonsList(result);
}

return py_result;
py::list pyGetConnectedFaces(
const py::array_t<float>& mesh_vertices_array,
const py::array_t<uint32_t>& mesh_indices_array,
const py::array_t<float>& mesh_uv_array,
const py::array_t<int32_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<const Point3F> mesh_vertices = std::span(static_cast<const Point3F*>(mesh_vertices_buffer.ptr), mesh_vertices_buffer.shape[0]);

pybind11::buffer_info mesh_indices_buffer = mesh_indices_array.request();
const std::span<const Face> mesh_indices = std::span(static_cast<const Face*>(mesh_indices_buffer.ptr), mesh_indices_buffer.shape[0]);

pybind11::buffer_info mesh_uv_buffer = mesh_uv_array.request();
const std::span<const Point2F> mesh_uv = std::span(static_cast<const Point2F*>(mesh_uv_buffer.ptr), mesh_uv_buffer.shape[0]);

pybind11::buffer_info mesh_faces_connectivity_buffer = mesh_faces_connectivity_array.request();
const std::span<const FaceSigned> mesh_faces_connectivity = std::span(static_cast<FaceSigned*>(mesh_faces_connectivity_buffer.ptr), mesh_faces_connectivity_buffer.shape[0]);

const std::vector<Polygon> 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)
Expand All @@ -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.");
}
4 changes: 2 additions & 2 deletions src/Triangle3F.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Triangle3F::Triangle3F(const Point3F& p1, const Point3F& p2, const Point3F& p3)
{
}

Vector3F Triangle3F::normal() const
std::optional<Vector3F> Triangle3F::normal() const
{
return Vector3F(p1_, p2_).cross(Vector3F(p1_, p3_));
return Vector3F(p1_, p2_).cross(Vector3F(p1_, p3_)).normalized();
}
96 changes: 88 additions & 8 deletions src/project.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,19 @@ std::vector<Polygon> toPolygons(const ClipperLib::Paths& paths)
return result;
}

std::vector<Polygon> unionPolygons(const std::vector<Polygon>& 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<Polygon> doProject(
const std::span<Point2F>& stroke_polygon,
const std::span<const Point3F>& mesh_vertices,
Expand Down Expand Up @@ -186,9 +199,9 @@ std::vector<Polygon> 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<Vector3F> 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;
Expand Down Expand Up @@ -233,13 +246,80 @@ std::vector<Polygon> doProject(
}
}

ClipperLib::Paths uv_areas_path;
uv_areas_path.reserve(result.size());
for (const Polygon& polygon : result)
return unionPolygons(result);
}

std::vector<Polygon> doGetConnectedFaces(
const std::span<const Point3F>& mesh_vertices,
const std::span<const Face>& mesh_indices,
const std::span<const Point2F>& mesh_uv,
const std::span<const FaceSigned>& 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<Vector3F> 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<Polygon> result;
std::unordered_set<uint32_t> visited_faces;
std::queue<uint32_t> 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<Vector3F> 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);
}