Skip to content

Commit db3e92e

Browse files
authored
Feat/line2 d type (#34)
* Added Line2D & LineSegment2D * polar_parametrization doxygen * polar_parametrization tests
1 parent e2f9306 commit db3e92e

2 files changed

Lines changed: 138 additions & 0 deletions

File tree

vortex_utils/cpp_test/test_types.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,74 @@ TEST_F(TypesTests, test_twist) {
143143
EXPECT_NEAR(diff.q, 0.3, 1e-12);
144144
EXPECT_NEAR(diff.r, 1.4, 1e-12);
145145
}
146+
147+
TEST_F(TypesTests, test_line_segment_2d) {
148+
vortex::utils::types::LineSegment2D line_segment;
149+
line_segment.p0 = {1.0, 0.2};
150+
line_segment.p1 = {0.4, 1.2};
151+
152+
vortex::utils::types::Line2D line2d = line_segment.polar_parametrization();
153+
154+
double expected_rho = 0.96039207679805; // sqrt(13)
155+
double expected_theta = 0.540419500270584; // atan2(4, 3)
156+
157+
EXPECT_NEAR(line2d.rho, expected_rho, 1e-12);
158+
EXPECT_NEAR(line2d.theta, expected_theta, 1e-12);
159+
}
160+
161+
TEST_F(TypesTests, test_line_segment_vertical) {
162+
vortex::utils::types::LineSegment2D line_segment;
163+
line_segment.p0 = {1.0, 0.0};
164+
line_segment.p1 = {1.0, 1.2};
165+
166+
vortex::utils::types::Line2D line2d = line_segment.polar_parametrization();
167+
168+
double expected_rho = 1.0;
169+
double expected_theta = 0.0;
170+
171+
EXPECT_NEAR(line2d.rho, expected_rho, 1e-12);
172+
EXPECT_NEAR(line2d.theta, expected_theta, 1e-12);
173+
}
174+
175+
TEST_F(TypesTests, test_line_segment_horizontal) {
176+
vortex::utils::types::LineSegment2D line_segment;
177+
line_segment.p0 = {1.2, 1.2};
178+
line_segment.p1 = {0, 1.2};
179+
180+
vortex::utils::types::Line2D line2d = line_segment.polar_parametrization();
181+
182+
double expected_rho = 1.2;
183+
double expected_theta = M_PI / 2.0;
184+
185+
EXPECT_NEAR(line2d.rho, expected_rho, 1e-12);
186+
EXPECT_NEAR(line2d.theta, expected_theta, 1e-12);
187+
}
188+
189+
TEST_F(TypesTests, test_line_segment_zero_length) {
190+
vortex::utils::types::LineSegment2D line_segment;
191+
line_segment.p0 = {0.0, 0.0};
192+
line_segment.p1 = {0.0, 0.0};
193+
194+
ASSERT_THROW(line_segment.polar_parametrization(), std::runtime_error);
195+
}
196+
197+
TEST_F(TypesTests, test_line_segment_wrap_theta) {
198+
vortex::utils::types::LineSegment2D line_segment1;
199+
double epsilon = 0.01;
200+
line_segment1.p0 = {0.4, -0.2};
201+
line_segment1.p1 = {0.4 - epsilon, 0.4};
202+
203+
vortex::utils::types::Line2D line2d1 =
204+
line_segment1.polar_parametrization();
205+
206+
vortex::utils::types::LineSegment2D line_segment2;
207+
line_segment2.p0 = {0.4, -0.2};
208+
line_segment2.p1 = {0.4 + epsilon, 0.4};
209+
210+
vortex::utils::types::Line2D line2d2 =
211+
line_segment2.polar_parametrization();
212+
213+
EXPECT_NEAR(line2d1.rho, line2d2.rho, 0.1);
214+
215+
EXPECT_NEAR(line2d1.theta + 2 * M_PI, line2d2.theta, 0.1);
216+
}

vortex_utils/include/vortex/utils/types.hpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#ifndef VORTEX_UTILS_TYPES_HPP
22
#define VORTEX_UTILS_TYPES_HPP
33

4+
#include <cmath>
45
#include <eigen3/Eigen/Core>
56
#include <eigen3/Eigen/Dense>
7+
#include <stdexcept>
68
#include "math.hpp"
79

810
namespace vortex::utils::types {
@@ -25,6 +27,20 @@ struct Pose;
2527
*/
2628
struct Twist;
2729

30+
/**
31+
* @brief Polar (Hesse normal) representation of a 2D line.
32+
*
33+
* @details
34+
* - rho is the signed distance from the origin to the line along the normal.
35+
* - theta is the angle (in radians) from the +x axis to the line's unit normal.
36+
*/
37+
struct Line2D;
38+
39+
/**
40+
* @brief Struct to represent a 2D line segment.
41+
*/
42+
struct LineSegment2D;
43+
2844
struct PoseEuler {
2945
double x{};
3046
double y{};
@@ -328,6 +344,57 @@ inline PoseEuler Pose::as_pose_euler() const {
328344
return eta;
329345
}
330346

347+
struct Point2D {
348+
double x{};
349+
double y{};
350+
};
351+
352+
struct Line2D {
353+
double rho{}; ///< Distance from origin to the line in canonical form.
354+
double theta{}; ///< Angle (rad) from +x axis to the unit normal.
355+
};
356+
357+
struct LineSegment2D {
358+
Point2D p0{};
359+
Point2D p1{};
360+
361+
/**
362+
* @brief Get the polar (Hesse normal) parametrization of the line segment.
363+
* Enforces positive rho and theta in [0, 2pi).
364+
* @return Line2D with rho and theta
365+
*/
366+
Line2D polar_parametrization() const {
367+
const double dx = p1.x - p0.x;
368+
const double dy = p1.y - p0.y;
369+
370+
const double len = std::hypot(dx, dy);
371+
372+
if (len <= std::numeric_limits<double>::epsilon()) {
373+
throw std::runtime_error("Invalid length for line segment");
374+
}
375+
376+
const double nx = -dy / len;
377+
const double ny = dx / len;
378+
379+
double rho = nx * p0.x + ny * p0.y;
380+
double theta = std::atan2(ny, nx); // (-pi, pi]
381+
382+
// Enforce positive rho
383+
if (rho < 0) {
384+
rho = -rho;
385+
theta += M_PI;
386+
}
387+
388+
theta = std::fmod(theta, 2.0 * M_PI);
389+
390+
if (theta < 0) {
391+
theta += 2.0 * M_PI;
392+
}
393+
394+
return Line2D{.rho = rho, .theta = theta};
395+
}
396+
};
397+
331398
} // namespace vortex::utils::types
332399

333400
#endif // VORTEX_UTILS_TYPES_HPP

0 commit comments

Comments
 (0)