Skip to content
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
6 changes: 3 additions & 3 deletions include/OpenABF/ABF.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,11 @@ class ABF
for (auto& f : mesh->faces()) {
f->lambda_tri += delta(idx++, 0);
}
auto base = edgeCnt + faceCnt;
for (auto& v : mesh->vertices_interior()) {
auto intIdx = vIdx2vIntIdx.at(v->idx);
v->lambda_plan += delta(idx + intIdx, 0);
v->lambda_len += delta(idx + vIntCnt + intIdx, 0);
idx++;
v->lambda_plan += delta(base + intIdx, 0);
v->lambda_len += delta(base + vIntCnt + intIdx, 0);
}

// Recalculate gradient for next iteration
Expand Down
16 changes: 8 additions & 8 deletions include/OpenABF/Vec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,24 +107,24 @@ class Vec
constexpr const_iterator cend() const noexcept { return val_.cend(); }

/** @brief Get an iterator to the first element of the reverse vector */
constexpr iterator rbegin() noexcept { return val_.rbegin(); }
/** @brief Get an iterator to the first element of the vector */
constexpr const_iterator rbegin() const noexcept { return val_.rbegin(); }
/** @brief Get an iterator to the first element of the vector */
constexpr const_iterator crbegin() const noexcept { return val_.crbegin(); }
constexpr reverse_iterator rbegin() noexcept { return val_.rbegin(); }
/** @brief Get an iterator to the first element of the reverse vector */
constexpr const_reverse_iterator rbegin() const noexcept { return val_.rbegin(); }
/** @brief Get an iterator to the first element of the reverse vector */
constexpr const_reverse_iterator crbegin() const noexcept { return val_.crbegin(); }

/**
* @brief Get an iterator to one past the last element in the reverse vector
*/
constexpr iterator rend() noexcept { return val_.rend(); }
constexpr reverse_iterator rend() noexcept { return val_.rend(); }
/**
* @brief Get an iterator to one past the last element in the reverse vector
*/
constexpr const_iterator rend() const noexcept { return val_.rend(); }
constexpr const_reverse_iterator rend() const noexcept { return val_.rend(); }
/**
* @brief Get an iterator to one past the last element in the reverse vector
*/
constexpr const_iterator crend() const noexcept { return val_.crend(); }
constexpr const_reverse_iterator crend() const noexcept { return val_.crend(); }

/** @brief Return whether the vector is empty (uninitialized) */
constexpr bool empty() const noexcept { return val_.empty(); }
Expand Down
22 changes: 11 additions & 11 deletions single_include/OpenABF/OpenABF.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,24 +259,24 @@ class Vec
constexpr const_iterator cend() const noexcept { return val_.cend(); }

/** @brief Get an iterator to the first element of the reverse vector */
constexpr iterator rbegin() noexcept { return val_.rbegin(); }
/** @brief Get an iterator to the first element of the vector */
constexpr const_iterator rbegin() const noexcept { return val_.rbegin(); }
/** @brief Get an iterator to the first element of the vector */
constexpr const_iterator crbegin() const noexcept { return val_.crbegin(); }
constexpr reverse_iterator rbegin() noexcept { return val_.rbegin(); }
/** @brief Get an iterator to the first element of the reverse vector */
constexpr const_reverse_iterator rbegin() const noexcept { return val_.rbegin(); }
/** @brief Get an iterator to the first element of the reverse vector */
constexpr const_reverse_iterator crbegin() const noexcept { return val_.crbegin(); }

/**
* @brief Get an iterator to one past the last element in the reverse vector
*/
constexpr iterator rend() noexcept { return val_.rend(); }
constexpr reverse_iterator rend() noexcept { return val_.rend(); }
/**
* @brief Get an iterator to one past the last element in the reverse vector
*/
constexpr const_iterator rend() const noexcept { return val_.rend(); }
constexpr const_reverse_iterator rend() const noexcept { return val_.rend(); }
/**
* @brief Get an iterator to one past the last element in the reverse vector
*/
constexpr const_iterator crend() const noexcept { return val_.crend(); }
constexpr const_reverse_iterator crend() const noexcept { return val_.crend(); }

/** @brief Return whether the vector is empty (uninitialized) */
constexpr bool empty() const noexcept { return val_.empty(); }
Expand Down Expand Up @@ -2301,11 +2301,11 @@ class ABF
for (auto& f : mesh->faces()) {
f->lambda_tri += delta(idx++, 0);
}
auto base = edgeCnt + faceCnt;
for (auto& v : mesh->vertices_interior()) {
auto intIdx = vIdx2vIntIdx.at(v->idx);
v->lambda_plan += delta(idx + intIdx, 0);
v->lambda_len += delta(idx + vIntCnt + intIdx, 0);
idx++;
v->lambda_plan += delta(base + intIdx, 0);
v->lambda_len += delta(base + vIntCnt + intIdx, 0);
}

// Recalculate gradient for next iteration
Expand Down
20 changes: 20 additions & 0 deletions tests/src/TestHalfEdgeMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,26 @@ TEST(HalfEdgeMesh, FindPath)
EXPECT_EQ(indices, expected);
}

TEST(HalfEdgeMesh, FindPath_Disconnected)
{
// Build two disconnected triangles (separate connected components)
const auto mesh = MeshType::New();
// CC1: vertices 0-2
mesh->insert_vertex(0, 0, 0);
mesh->insert_vertex(1, 0, 0);
mesh->insert_vertex(0, 1, 0);
mesh->insert_face(0, 2, 1);
// CC2: vertices 3-5
mesh->insert_vertex(5, 0, 0);
mesh->insert_vertex(6, 0, 0);
mesh->insert_vertex(5, 1, 0);
mesh->insert_face(3, 5, 4);

// Path between vertices in different CCs should be empty
const auto path = FindEdgePath(mesh, 0, 3);
EXPECT_TRUE(path.empty());
}

TEST(HalfEdgeMesh, Clone)
{
const auto mesh = ConstructPyramid<MeshType>();
Expand Down
40 changes: 40 additions & 0 deletions tests/src/TestMeshIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,46 @@ TEST(MeshIO, PLY_ReadWrite)
EXPECT_EQ(mesh->num_faces(), mesh2->num_faces());
}

TEST(MeshIO, OBJ_RoundTrip)
{
auto mesh = ConstructPyramid<MeshType>();

std::ostringstream oss;
io_formats::OBJ::Write(oss, *mesh);

auto mesh2 = MeshType::New();
std::istringstream iss(oss.str());
io_formats::OBJ::Read(iss, *mesh2);

ASSERT_EQ(mesh->num_vertices(), mesh2->num_vertices());
ASSERT_EQ(mesh->num_faces(), mesh2->num_faces());
for (std::size_t v = 0; v < mesh->num_vertices(); ++v) {
for (int i = 0; i < 3; ++i) {
EXPECT_FLOAT_EQ(mesh->vertex(v)->pos[i], mesh2->vertex(v)->pos[i]);
}
}
}

TEST(MeshIO, PLY_RoundTrip)
{
auto mesh = ConstructPyramid<MeshType>();

std::ostringstream oss;
io_formats::PLY::Write(oss, *mesh);

auto mesh2 = MeshType::New();
std::istringstream iss(oss.str());
io_formats::PLY::Read(iss, *mesh2);

ASSERT_EQ(mesh->num_vertices(), mesh2->num_vertices());
ASSERT_EQ(mesh->num_faces(), mesh2->num_faces());
for (std::size_t v = 0; v < mesh->num_vertices(); ++v) {
for (int i = 0; i < 3; ++i) {
EXPECT_FLOAT_EQ(mesh->vertex(v)->pos[i], mesh2->vertex(v)->pos[i]);
}
}
}

TEST(MeshIO, ReadWriteFile)
{
auto mesh = ConstructPyramid<MeshType>();
Expand Down
72 changes: 72 additions & 0 deletions tests/src/TestParameterization.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <cmath>
#include <gtest/gtest.h>

#include "OpenABF/OpenABF.hpp"
Expand Down Expand Up @@ -62,4 +63,75 @@ TEST(Parameterizations, ABFPlusPlus)
EXPECT_FLOAT_EQ(vv->pos[i], ve[i]);
}
}
}

TEST(Parameterizations, ABF_MultiInterior)
{
// 4×4 vertex grid → 4 interior vertices; exercises the multi-interior code path
using ABFType = ABF<float>;
using LSCM = AngleBasedLSCM<float, ABFType::Mesh>;

auto mesh = ConstructGrid<ABFType::Mesh>(4, 4);
ASSERT_EQ(mesh->num_vertices_interior(), 4u);

ABFType::Compute(mesh);
LSCM::Compute(mesh);

// After LSCM, all UV z-coordinates must be zero and x/y must be finite
for (std::size_t v = 0; v < mesh->num_vertices(); ++v) {
const auto& pos = mesh->vertex(v)->pos;
EXPECT_TRUE(std::isfinite(pos[0])) << "vertex " << v << " x is not finite";
EXPECT_TRUE(std::isfinite(pos[1])) << "vertex " << v << " y is not finite";
EXPECT_FLOAT_EQ(pos[2], 0.f);
}
}

TEST(Parameterizations, ABFPlusPlus_MultiInterior)
{
// 4×4 vertex grid → 4 interior vertices
using ABFType = ABFPlusPlus<float>;
using LSCM = AngleBasedLSCM<float, ABFType::Mesh>;

auto mesh = ConstructGrid<ABFType::Mesh>(4, 4);
ASSERT_EQ(mesh->num_vertices_interior(), 4u);

ABFType::Compute(mesh);
LSCM::Compute(mesh);

for (std::size_t v = 0; v < mesh->num_vertices(); ++v) {
const auto& pos = mesh->vertex(v)->pos;
EXPECT_TRUE(std::isfinite(pos[0])) << "vertex " << v << " x is not finite";
EXPECT_TRUE(std::isfinite(pos[1])) << "vertex " << v << " y is not finite";
EXPECT_FLOAT_EQ(pos[2], 0.f);
}
}

TEST(Parameterizations, ABF_NoInteriorVertices)
{
// Single triangle: 0 interior vertices — solver must not crash
using ABFType = ABF<float>;
using LSCM = AngleBasedLSCM<float, ABFType::Mesh>;

auto mesh = ABFType::Mesh::New();
mesh->insert_vertex(0, 0, 0);
mesh->insert_vertex(1, 0, 0);
mesh->insert_vertex(0, 1, 0);
mesh->insert_face(0, 2, 1);

ASSERT_EQ(mesh->num_vertices_interior(), 0u);
EXPECT_NO_THROW(ABFType::Compute(mesh));
EXPECT_NO_THROW(LSCM::Compute(mesh));
}

TEST(Parameterizations, ABF_MaxIters)
{
// Instance API: limit to 1 iteration and confirm the solver respects it
using ABFType = ABF<float>;

auto mesh = ConstructPyramid<ABFType::Mesh>();
ABFType abf;
abf.setMaxIterations(1);
abf.compute(mesh);

EXPECT_EQ(abf.iterations(), 1u);
}
20 changes: 20 additions & 0 deletions tests/src/TestVec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,24 @@ TEST(Vec, UnitVector)
Vec3f a{2, 0, 0};
EXPECT_EQ(a.unit(), Vec3f(1, 0, 0));
EXPECT_EQ(a, Vec3f(2, 0, 0));
}

TEST(Vec, ReverseIteration)
{
Vec3f v{1.f, 2.f, 3.f};

// rbegin/rend
std::vector<float> result;
for (auto it = v.rbegin(); it != v.rend(); ++it) {
result.push_back(*it);
}
EXPECT_EQ(result, (std::vector<float>{3.f, 2.f, 1.f}));

// crbegin/crend on a const Vec3f
const Vec3f cv{1.f, 2.f, 3.f};
std::vector<float> cresult;
for (auto it = cv.crbegin(); it != cv.crend(); ++it) {
cresult.push_back(*it);
}
EXPECT_EQ(cresult, (std::vector<float>{3.f, 2.f, 1.f}));
}
28 changes: 28 additions & 0 deletions tests/src/Utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,32 @@ auto ConstructPyramid() -> typename MeshType::Pointer
mesh->insert_faces({{1, 3, 0}, {3, 2, 0}, {3, 1, 2}});
return mesh;
}

/**
* Construct a flat rectangular grid mesh, triangulated by splitting each quad
* along its diagonal. A (rows x cols) vertex grid produces (rows-1)*(cols-1)*2
* triangles. Interior vertex count = (rows-2)*(cols-2).
*
* Example: ConstructGrid<MeshType>(4, 4) → 16 vertices, 18 faces, 4 interior vertices.
*/
template <typename MeshType>
auto ConstructGrid(std::size_t rows, std::size_t cols) -> typename MeshType::Pointer
{
auto mesh = MeshType::New();
for (std::size_t r = 0; r < rows; ++r) {
for (std::size_t c = 0; c < cols; ++c) {
mesh->insert_vertex(static_cast<float>(c), static_cast<float>(r), 0.f);
}
}
for (std::size_t r = 0; r < rows - 1; ++r) {
for (std::size_t c = 0; c < cols - 1; ++c) {
auto v0 = r * cols + c;
auto v1 = r * cols + c + 1;
auto v2 = (r + 1) * cols + c;
auto v3 = (r + 1) * cols + c + 1;
mesh->insert_faces({{v0, v2, v1}, {v1, v2, v3}});
}
}
return mesh;
}
} // namespace OpenABF::tests
Loading