@@ -1089,43 +1089,250 @@ std::vector<Triangle> grow_surface_from_seed(
10891089
10901090
10911091navmap::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+
11291336navmap::NavMap from_pointcloud2 (
11301337 const sensor_msgs::msg::PointCloud2 & pc2,
11311338 navmap_ros_interfaces::msg::NavMap & out_msg,
0 commit comments