#ifndef HOLE_DETECTION_H #define HOLE_DETECTION_H #include "HoleDetectionParams.h" #include "ErrorCodes.h" #include "VZNL_Types.h" // Export macro for shared library #ifdef _WIN32 #ifdef HOLE_DETECTION_EXPORTS #define HOLE_DETECTION_API __declspec(dllexport) #else #define HOLE_DETECTION_API __declspec(dllimport) #endif #else #ifdef HOLE_DETECTION_EXPORTS #define HOLE_DETECTION_API __attribute__((visibility("default"))) #else #define HOLE_DETECTION_API #endif #endif /** * @brief Boundary point with grid location (for debug callbacks) */ struct SHoleBoundaryPoint { SVzNLPointXYZ point; // 点坐标 int row; // 行号 int col; // 列号 SHoleBoundaryPoint() : point(), row(-1), col(-1) {} SHoleBoundaryPoint(const SVzNLPointXYZ& p, int r, int c) : point(p), row(r), col(c) {} }; /** * @brief Per-point signed angle state along one scanned line */ enum ELineAngleTrend { keLineAngleTrend_Invalid = 0, keLineAngleTrend_Flat = 1, keLineAngleTrend_PositiveJump = 2, keLineAngleTrend_NegativeJump = 3 }; /** * @brief Angle profile sample for one valid point on a scanned line */ struct SLineAngleSample { SVzNLPointXYZ point; // Current point int row; // Grid row int col; // Grid column int linePos; // Position inside the scanned line float signedAngleDeg; // Signed turning angle in degrees float beforeMeanZ; // Mean Z of points found within A before current point float afterMeanZ; // Mean Z of points found within A after current point float beforeCoord; // Projected coord of the furthest point before current point float afterCoord; // Projected coord of the furthest point after current point ELineAngleTrend trend; // Flat / positive jump / negative jump unsigned char hasAngle; // 1 if both sides have enough points to form an angle SLineAngleSample() : point() , row(-1) , col(-1) , linePos(-1) , signedAngleDeg(0.0f) , beforeMeanZ(0.0f) , afterMeanZ(0.0f) , beforeCoord(0.0f) , afterCoord(0.0f) , trend(keLineAngleTrend_Invalid) , hasAngle(0U) {} }; /** * @brief Cluster information for debug callbacks */ struct SClusterInfo { const SHoleBoundaryPoint* points; // Points in this cluster int count; // Number of points in this cluster }; /** * @brief Plane segmentation information for debug callbacks */ struct SPlaneSegmentInfo { float normalX, normalY, normalZ; // 平面法向量 (归一化) int pointCount; // 该平面的点数 const int* pointIndices; // 点索引数组 (在原始grid中的索引) float normalAngleDeg; // 法向量与参考方向的夹角 (度) }; /** * @brief Debug callbacks for visualization and debugging * * All callbacks are optional (can be nullptr). * userData is passed to all callbacks for user context. */ struct SHoleDetectionDebugCallbacks { /** * @brief Called after boundary points are detected * @param points Array of boundary points * @param count Number of boundary points * @param userData User-provided context pointer */ void (*onBoundaryDetected)(const SHoleBoundaryPoint* points, int count, void* userData); /** * @brief Called after clustering is complete * @param clusters Array of cluster information * @param clusterCount Number of clusters found * @param userData User-provided context pointer */ void (*onClustersFound)(const SClusterInfo* clusters, int clusterCount, void* userData); /** * @brief Called when expanded region points are collected for plane fitting * @param clusterPoints Array of cluster boundary points * @param clusterCount Number of cluster points * @param expandedPoints Array of expanded region points (surrounding flat surface) * @param expandedCount Number of expanded region points * @param userData User-provided context pointer */ void (*onExpandedRegion)(const SHoleBoundaryPoint* clusterPoints, int clusterCount, const SVzNLPointXYZ* expandedPoints, int expandedCount, void* userData); /** * @brief Called after each hole is fitted * @param hole The fitted hole result * @param holeIndex Index of this hole (0-based) * @param userData User-provided context pointer */ void (*onHoleFitted)(const SHoleResult* hole, int holeIndex, void* userData); /** * @brief Called when segment endpoint pairs are detected * @param segmentPairs Array of segment pairs (each pair has start and end points) * @param count Number of segment pairs * @param userData User-provided context pointer */ void (*onSegmentPairsDetected)(const SSegmentPair* segmentPairs, int count, void* userData); /** * @brief Called when one scanned line's signed angle profile is available * @param samples Array of valid-point angle samples in line order * @param count Number of samples * @param lineType "Row" or "Column" * @param lineIndex Row/column index * @param userData User-provided context pointer */ void (*onLineAngleProfileDetected)( const SLineAngleSample* samples, int count, const char* lineType, int lineIndex, void* userData ); /** * @brief Called after RANSAC plane segmentation and normal filtering * @param planes Array of plane segment information * @param planeCount Number of planes * @param totalPointCount Total number of points in the grid (rows * cols) * @param userData User-provided context pointer */ void (*onPlanesSegmented)( const SPlaneSegmentInfo* planes, int planeCount, int totalPointCount, void* userData ); /** * @brief Called after masked points grid is built for a plane * @param points Masked point array (grid format, rows * cols) * @param rows Row count * @param cols Column count * @param planeIndex Index of the current plane (0-based) * @param userData User-provided context pointer */ void (*onMaskedPointsReady)( const SVzNLPointXYZ* points, int rows, int cols, int planeIndex, void* userData ); /** * @brief User-provided context pointer, passed to all callbacks */ void* userData; /** * @brief If non-null, each cluster's boundary points are saved as a CSV file * in this directory before fitting. Files are named cluster_0.csv, cluster_1.csv, ... * Fields: row,col,x,y,z */ const char* clusterOutputDir; }; /** * @brief Detect multiple holes in point cloud * * This is the main entry point for hole detection. It performs the following steps: * 1. Validate grid format * 2. Detect pit boundaries * 3. Cluster boundary points * 4. Fit ellipse and plane for each cluster * 5. Filter and sort results * * @param [in] points Input point cloud (grid format) * @param [in] rows Number of rows * @param [in] cols Number of columns * @param [in] detectionParams Detection parameters * @param [in] filterParams Filter parameters * @param [out] result Output result structure * @param [in] debugCallbacks Optional debug callbacks for visualization (can be nullptr) * @return 0 on success, non-zero on error * * @pre points != nullptr * @pre rows > 0 * @pre cols > 0 * @pre result != nullptr * * @post If return value is 0, result contains detected holes * @post Caller must free result->holes using delete[] */ HOLE_DETECTION_API int DetectMultipleHoles( const SVzNLPointXYZ* points, int rows, int cols, const RansacPlaneSegmentationParams& ransacParams, const SHoleDetectionParams& detectionParams, const SHoleFilterParams& filterParams, SMultiHoleResult* result, const SHoleDetectionDebugCallbacks* debugCallbacks = nullptr ); /** * @brief Get algorithm name and version information * * Returned pointers refer to internal static strings and must not be freed * or modified by the caller. Either output parameter can be nullptr. * * @param [out] algorithmName Output algorithm name string * @param [out] algorithmVersion Output algorithm version string */ HOLE_DETECTION_API const char* GetAlgorithmVersion(); HOLE_DETECTION_API const char* GetAlgorithmName(); /** * @brief Print detection results to console in a formatted way * * @param [in] result Detection result structure * @param [in] verbose If true, print detailed information for each hole */ HOLE_DETECTION_API void PrintDetectionResults(const SMultiHoleResult& result, bool verbose = true); #endif // HOLE_DETECTION_H