Skip to content

Commit 24be6e8

Browse files
authored
Merge pull request #6 from EasyNavigation/imp_surf
Imp surf
2 parents b349fa9 + bdfda1a commit 24be6e8

3 files changed

Lines changed: 234 additions & 25 deletions

File tree

navmap_ros/include/navmap_ros/conversions.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ struct BuildParams
193193
/** @brief Maximum allowed edge length (meters) when forming triangles. */
194194
float max_edge_len = 2.0;
195195

196+
/** @brief Maximum allowed vertical jump (meters) between vertices of a triangle. */
197+
float max_dz = 0.25f;
198+
196199
/** @brief Maximum slope with respect to the vertical axis (degrees). */
197200
float max_slope_deg = 30.0f; // maximum slope w.r.t. vertical
198201

navmap_ros/src/navmap_ros/conversions.cpp

Lines changed: 228 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,43 +1089,250 @@ std::vector<Triangle> grow_surface_from_seed(
10891089

10901090

10911091
navmap::NavMap from_points(
1092-
const pcl::PointCloud<pcl::PointXYZ> & input_points,
1092+
const pcl::PointCloud<pcl::PointXYZ> & input_points,
10931093
navmap_ros_interfaces::msg::NavMap & out_msg,
10941094
BuildParams params)
10951095
{
1096-
auto downsampled_points = downsample_voxelize_avgXYZ(input_points, params.resolution);
1096+
// --- Overview ------------------------------------------------------------
1097+
// 1) Optional voxel downsampling in XY (averaging XYZ).
1098+
// 2) 2D Delaunay triangulation in the XY plane (no crossing edges by construction).
1099+
// 3) Lift triangles back to 3D and filter by:
1100+
// - area bounds,
1101+
// - maximum edge length,
1102+
// - maximum vertical discontinuity per edge (max_dz),
1103+
// - maximum slope w.r.t. +Z (max_slope_deg).
1104+
// 4) Emit a single-surface NavMap and mirror it into out_msg.
1105+
// -------------------------------------------------------------------------
1106+
1107+
using std::vector;
1108+
1109+
if (input_points.empty() || input_points.size() < 3) {
1110+
out_msg = navmap_ros_interfaces::msg::NavMap();
1111+
return navmap::NavMap();
1112+
}
10971113

1098-
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
1099-
kdtree.setInputCloud(downsampled_points.makeShared());
1114+
pcl::PointCloud<pcl::PointXYZ> cloud;
1115+
if (params.resolution > 0.0f) {
1116+
cloud = downsample_voxelize_avgXYZ(input_points, params.resolution);
1117+
} else {
1118+
cloud = input_points;
1119+
}
1120+
if (cloud.size() < 3) {
1121+
out_msg = navmap_ros_interfaces::msg::NavMap();
1122+
return navmap::NavMap();
1123+
}
11001124

1101-
pcl::PointXYZ origin(params.seed.x(), params.seed.y(), params.seed.z());
1125+
struct Pt2 { double x, y; int idx3d; };
1126+
struct Tri2 { int a, b, c; };
11021127

1103-
std::vector<int> nbr_indices;
1104-
std::vector<float> nbr_dists;
1128+
auto orient2d = [](const Pt2 & a, const Pt2 & b, const Pt2 & c) -> double {
1129+
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
1130+
};
11051131

1106-
nbr_indices.clear(); nbr_dists.clear();
1107-
bool found = kdtree.radiusSearch(origin, params.resolution, nbr_indices, nbr_dists);
1132+
auto in_circumcircle = [&](const Pt2 & p,
1133+
const Pt2 & A, const Pt2 & B, const Pt2 & C) -> bool
1134+
{
1135+
const double ax = A.x - p.x, ay = A.y - p.y;
1136+
const double bx = B.x - p.x, by = B.y - p.y;
1137+
const double cx = C.x - p.x, cy = C.y - p.y;
1138+
const double det = (ax * ax + ay * ay) * (bx * cy - by * cx) -
1139+
(bx * bx + by * by) * (ax * cy - ay * cx) +
1140+
(cx * cx + cy * cy) * (ax * by - ay * bx);
1141+
const double o = orient2d(A, B, C);
1142+
return (o > 0.0) ? (det > 0.0) : (det < 0.0);
1143+
};
11081144

1109-
if (!found) {
1110-
std::cerr << "Unable to find surface near seed" << std::endl;
1111-
return {};
1145+
vector<Pt2> pts2; pts2.reserve(cloud.size());
1146+
for (int i = 0; i < static_cast<int>(cloud.size()); ++i) {
1147+
pts2.push_back({static_cast<double>(cloud[i].x),
1148+
static_cast<double>(cloud[i].y), i});
11121149
}
11131150

1114-
int seed = nbr_indices[0];
1151+
double minx = 1e300, miny = 1e300, maxx = -1e300, maxy = -1e300;
1152+
for (const auto & p : pts2) {
1153+
if (p.x < minx) {minx = p.x;}
1154+
if (p.y < miny) {miny = p.y;}
1155+
if (p.x > maxx) {maxx = p.x;}
1156+
if (p.y > maxy) {maxy = p.y;}
1157+
}
1158+
const double dx = maxx - minx, dy = maxy - miny, d = std::max(dx, dy);
1159+
Pt2 S1{minx - 10 * d, miny - d, -1};
1160+
Pt2 S2{minx + 0.5 * d, maxy + 10 * d, -2};
1161+
Pt2 S3{maxx + 10 * d, miny - d, -3};
1162+
1163+
vector<Pt2> vs = pts2;
1164+
const int iS1 = static_cast<int>(vs.size()); vs.push_back(S1);
1165+
const int iS2 = static_cast<int>(vs.size()); vs.push_back(S2);
1166+
const int iS3 = static_cast<int>(vs.size()); vs.push_back(S3);
1167+
1168+
vector<Tri2> tris;
1169+
if (orient2d(vs[iS1], vs[iS2], vs[iS3]) <= 0.0) {
1170+
std::swap(vs[iS2], vs[iS3]);
1171+
}
1172+
tris.push_back({iS1, iS2, iS3});
1173+
1174+
// Incremental Bowyer–Watson
1175+
for (int pi = 0; pi < static_cast<int>(pts2.size()); ++pi) {
1176+
const Pt2 p = vs[pi];
1177+
1178+
// 2.1) Collect "bad" triangles (circumcircle contains p)
1179+
vector<int> bad; bad.reserve(tris.size());
1180+
for (int ti = 0; ti < static_cast<int>(tris.size()); ++ti) {
1181+
const auto & t = tris[ti];
1182+
if (in_circumcircle(p, vs[t.a], vs[t.b], vs[t.c])) {
1183+
bad.push_back(ti);
1184+
}
1185+
}
11151186

1116-
auto triangles = grow_surface_from_seed(downsampled_points, seed, params);
1187+
// 2.2) Boundary of the polygonal cavity: edges touched exactly once
1188+
struct EdgeKey
1189+
{
1190+
int u, v;
1191+
bool operator==(const EdgeKey & o) const noexcept
1192+
{
1193+
return u == o.u && v == o.v;
1194+
}
1195+
};
1196+
struct EdgeKeyHash
1197+
{
1198+
std::size_t operator()(const EdgeKey & e) const noexcept
1199+
{
1200+
return (static_cast<std::size_t>(e.u) << 32) ^ static_cast<std::size_t>(e.v);
1201+
}
1202+
};
11171203

1118-
navmap::NavMap navmap;
1119-
if (!navmap_ros::build_navmap_from_mesh(
1120-
downsampled_points, triangles, "map", out_msg, &navmap))
1121-
{
1122-
std::cerr << "Error building ::navmap::NavMap from mesh" << std::endl;
1123-
return {};
1204+
std::unordered_map<EdgeKey, int, EdgeKeyHash> edge_count;
1205+
edge_count.reserve(bad.size() * 3);
1206+
1207+
auto add_edge = [&](int u, int v) {
1208+
// canonicalize (min, max) so opposite directions collide
1209+
if (u > v) {std::swap(u, v);}
1210+
EdgeKey e{u, v};
1211+
++edge_count[e];
1212+
};
1213+
1214+
for (int ti : bad) {
1215+
const auto & t = tris[ti];
1216+
add_edge(t.a, t.b);
1217+
add_edge(t.b, t.c);
1218+
add_edge(t.c, t.a);
1219+
}
1220+
1221+
// 2.3) Remove bad triangles
1222+
vector<Tri2> kept; kept.reserve(tris.size());
1223+
vector<char> removed(tris.size(), 0);
1224+
for (int idx : bad) {
1225+
removed[idx] = 1;
1226+
}
1227+
for (int ti = 0; ti < static_cast<int>(tris.size()); ++ti) {
1228+
if (!removed[ti]) {kept.push_back(tris[ti]);}
1229+
}
1230+
tris.swap(kept);
1231+
1232+
// 2.4) Retriangulate the cavity with p
1233+
for (const auto & kv : edge_count) {
1234+
if (kv.second != 1) {continue;} // interior edges appear twice
1235+
int u = kv.first.u, v = kv.first.v;
1236+
// enforce CCW for (u, v, p)
1237+
if (orient2d(vs[u], vs[v], p) <= 0.0) {std::swap(u, v);}
1238+
tris.push_back({u, v, pi});
1239+
}
1240+
}
1241+
1242+
// 2.5) Discard triangles touching the super-triangle
1243+
vector<Tri2> final_tris; final_tris.reserve(tris.size());
1244+
for (const auto & t : tris) {
1245+
if (t.a >= static_cast<int>(pts2.size()) ||
1246+
t.b >= static_cast<int>(pts2.size()) ||
1247+
t.c >= static_cast<int>(pts2.size()))
1248+
{
1249+
continue;
1250+
}
1251+
final_tris.push_back(t);
11241252
}
11251253

1126-
return navmap;
1254+
// ---- 3) Lift to 3D and filter ------------------------------------------
1255+
auto tri_area3D = [](const Eigen::Vector3f & A,
1256+
const Eigen::Vector3f & B,
1257+
const Eigen::Vector3f & C) -> float {
1258+
return 0.5f * ((B - A).cross(C - A)).norm();
1259+
};
1260+
auto tri_normal = [](const Eigen::Vector3f & A,
1261+
const Eigen::Vector3f & B,
1262+
const Eigen::Vector3f & C) -> Eigen::Vector3f {
1263+
Eigen::Vector3f n = (B - A).cross(C - A);
1264+
const float L = n.norm();
1265+
return (L > 1e-12f) ? (n / L) : Eigen::Vector3f(0.f, 0.f, 1.f);
1266+
};
1267+
auto edge_len3D = [&](int i, int j) -> float {
1268+
const auto & a = cloud[i]; const auto & b = cloud[j];
1269+
const float dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z;
1270+
return std::sqrt(dx * dx + dy * dy + dz * dz);
1271+
};
1272+
1273+
const float cos_max_slope =
1274+
std::cos(params.max_slope_deg * static_cast<float>(M_PI) / 180.0f);
1275+
const float min_area = std::max(params.min_area, 1e-9f);
1276+
const float max_edge =
1277+
(params.max_edge_len > 0.0f) ?
1278+
params.max_edge_len :
1279+
std::numeric_limits<float>::infinity();
1280+
const float max_dz =
1281+
(params.max_dz > 0.0f) ?
1282+
params.max_dz :
1283+
std::numeric_limits<float>::infinity();
1284+
1285+
vector<Eigen::Vector3i> triangles;
1286+
triangles.reserve(final_tris.size());
1287+
1288+
// Debug counters
1289+
size_t dropped_area = 0, dropped_edge = 0, dropped_dz = 0, dropped_slope = 0;
1290+
1291+
for (const auto & t : final_tris) {
1292+
const int ia = vs[t.a].idx3d;
1293+
const int ib = vs[t.b].idx3d;
1294+
const int ic = vs[t.c].idx3d;
1295+
1296+
const Eigen::Vector3f A(cloud[ia].x, cloud[ia].y, cloud[ia].z);
1297+
const Eigen::Vector3f B(cloud[ib].x, cloud[ib].y, cloud[ib].z);
1298+
const Eigen::Vector3f C(cloud[ic].x, cloud[ic].y, cloud[ic].z);
1299+
1300+
const float area = tri_area3D(A, B, C);
1301+
if (area < min_area) {++dropped_area; continue;}
1302+
1303+
const float lAB = edge_len3D(ia, ib);
1304+
const float lBC = edge_len3D(ib, ic);
1305+
const float lCA = edge_len3D(ic, ia);
1306+
if (lAB > max_edge || lBC > max_edge || lCA > max_edge) {++dropped_edge; continue;}
1307+
1308+
const float dzAB = std::fabs(A.z() - B.z());
1309+
const float dzBC = std::fabs(B.z() - C.z());
1310+
const float dzCA = std::fabs(C.z() - A.z());
1311+
if (std::max({dzAB, dzBC, dzCA}) > max_dz) {++dropped_dz; continue;}
1312+
1313+
const Eigen::Vector3f n = tri_normal(A, B, C);
1314+
if (n.dot(Eigen::Vector3f::UnitZ()) < cos_max_slope) {++dropped_slope; continue;}
1315+
1316+
triangles.emplace_back(ia, ib, ic);
1317+
}
1318+
1319+
// ---- 4) Emit message + core --------------------------------------------
1320+
navmap::NavMap core;
1321+
build_navmap_from_mesh(cloud, triangles, /*frame_id*/ "map", out_msg, &core);
1322+
1323+
// ---- Debug report ------------------------------------------------------
1324+
// std::cerr << "[from_points] Candidate tris: " << final_tris.size()
1325+
// << " | Accepted: " << triangles.size()
1326+
// << " | Dropped (area=" << dropped_area
1327+
// << ", edge=" << dropped_edge
1328+
// << ", dz=" << dropped_dz
1329+
// << ", slope=" << dropped_slope
1330+
// << ")\n";
1331+
1332+
return core;
11271333
}
11281334

1335+
11291336
navmap::NavMap from_pointcloud2(
11301337
const sensor_msgs::msg::PointCloud2 & pc2,
11311338
navmap_ros_interfaces::msg::NavMap & out_msg,

navmap_ros/src/slam_server_app.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,9 @@ class SLAMServerNode : public rclcpp::Node
5353
[this](sensor_msgs::msg::PointCloud2::UniquePtr msg) {
5454
RCLCPP_INFO(get_logger(), "Creating and publishing NavMap from PointCloud2");
5555
navmap_ros::BuildParams params;
56-
params.max_edge_len = 1.5f;
57-
params.neighbor_radius = 1.5f;
58-
params.min_angle_deg = 15.0f;
59-
params.max_slope_deg = 20.0f;
56+
params.max_edge_len = 5.0f;
57+
params.min_angle_deg = 25.0f;
58+
params.max_slope_deg = 30.0f;
6059
params.resolution = 1.0f;
6160
navmap_ = navmap_ros::from_pointcloud2(*msg, navmap_msg_, params);
6261

0 commit comments

Comments
 (0)