From 0a2804a0f2e724ecfde93bb3eb88423bd3291b3b Mon Sep 17 00:00:00 2001 From: sladro Date: Fri, 19 Sep 2025 22:18:51 +0800 Subject: [PATCH] feat: implement 2D grid-based visibility analysis for shell detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace 1D projection occlusion with 2D grid-based z-buffer algorithm to fix lateral occlusion misjudgment. Key improvements: - Add ScreenGrid class with 96x96 resolution for accurate 2D visibility calculation - Replace single occlusion boundary with per-cell depth testing - Fix C++17 compatibility issues (structured bindings, Windows min/max macros) - Add debug logging for component visibility analysis (outputs to txt file) - Adjust voting thresholds: MIN_VOTES from 2 to 3, MIN_VISIBILITY_RATIO from 0.04 to 0.03 This fixes the issue where components in different lateral positions were incorrectly marked as occluded. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CreoManager.cpp | 129 +++++++++++++++++++++++++++++++++------ CreoManager.h | 159 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+), 19 deletions(-) diff --git a/CreoManager.cpp b/CreoManager.cpp index b0ff781..36eb605 100644 --- a/CreoManager.cpp +++ b/CreoManager.cpp @@ -31,8 +31,10 @@ // Shell Analysis Algorithm Constants const int SHELL_ANALYSIS_NUM_DIRECTIONS = 96; // 多方向投影采样数量 -const double SHELL_ANALYSIS_MIN_VISIBILITY_RATIO = 0.04; // 最小可见率:4%方向可见即为外壳候选 -const int SHELL_ANALYSIS_MIN_VOTES = 2; // 最少得票数:至少2个方向可见 +const int SHELL_ANALYSIS_GRID_RESOLUTION = 96; // 网格分辨率96x96 +const double SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR = 0.001; // 单方向最小可见占比0.1% +const double SHELL_ANALYSIS_MIN_VISIBILITY_RATIO = 0.03; // 最小可见率:3%方向可见即为外壳候选(降低阈值) +const int SHELL_ANALYSIS_MIN_VOTES = 3; // 最少得票数:至少3个方向可见(提高要求) const double SHELL_ANALYSIS_HIGH_VISIBILITY_THRESHOLD = 0.20; // 高可见度阈值:20%以上强制保留 const double SHELL_ANALYSIS_MEDIUM_VISIBILITY_THRESHOLD = 0.06; // 中等可见度阈值:6-20%需要审查 const double SHELL_ANALYSIS_OCCLUSION_TOLERANCE = 0.5; // 遮挡容差(当前未使用) @@ -2842,51 +2844,140 @@ CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjecti featureIdToName[comp.featureId] = comp.name; } - // Step 4: For each direction, determine visible components using occlusion detection + // Step 4: For each direction, determine visible components using 2D grid-based occlusion detection for (const Vector3D& direction : directions) { + // Create screen grid for this viewing direction + ScreenGrid grid(result.globalAABB, direction, SHELL_ANALYSIS_GRID_RESOLUTION); + // Structure to hold projection data struct ProjectionData { - double minProj; // Minimum projection value - double maxProj; // Maximum projection value + double minProj; // Minimum projection value (depth) int featureId; + bool useOBB; + const ComponentItem* comp; }; std::vector projections; projections.reserve(result.components.size()); - // Calculate projection ranges for all components + // Calculate projection depths for all components for (const ComponentItem& comp : result.components) { bool useOBB = ShouldUseOBB(comp); - double minProj, maxProj; + double minProj; if (useOBB) { auto range = CalculateOBBProjectionRange(comp.worldOBB, direction); minProj = range.first; - maxProj = range.second; } else { auto range = CalculateAABBProjectionRange(comp.worldAABB, direction); minProj = range.first; - maxProj = range.second; } - projections.push_back({minProj, maxProj, comp.featureId}); + projections.push_back({minProj, comp.featureId, useOBB, &comp}); } - // Sort by minimum projection value (nearest first) + // Sort by minimum projection value (nearest first) for efficient processing std::sort(projections.begin(), projections.end(), [](const ProjectionData& a, const ProjectionData& b) { return a.minProj < b.minProj; }); - // Occlusion detection: near components occlude far components - double occlusionBoundary = -std::numeric_limits::max(); - + // Project components to grid (front to back order) for (const auto& proj : projections) { - // Strict occlusion: component visible only if completely unoccluded - if (proj.minProj > occlusionBoundary) { - result.visibilityVotes[proj.featureId]++; - // Update occlusion boundary to this component's farthest point - occlusionBoundary = std::max(occlusionBoundary, proj.maxProj); + if (proj.useOBB) { + grid.projectOBB(proj.comp->worldOBB, direction, proj.minProj, proj.featureId); + } else { + grid.projectAABB(proj.comp->worldAABB, direction, proj.minProj, proj.featureId); + } + } + + // Get visibility counts from grid + auto visibilityCounts = grid.getVisibilityCount(); + int totalCells = grid.gridSizeU * grid.gridSizeV; + + // Update visibility votes based on grid visibility + for (const auto& kvp : visibilityCounts) { + int componentId = kvp.first; + int cellCount = kvp.second; + double visibilityRatio = (double)cellCount / totalCells; + + // Update continuous visibility ratio (max across all directions) + if (result.visibilityRatios.find(componentId) == result.visibilityRatios.end()) { + result.visibilityRatios[componentId] = visibilityRatio; + } else { + result.visibilityRatios[componentId] = std::max(result.visibilityRatios[componentId], visibilityRatio); + } + + // Vote if component meets minimum visibility threshold for this direction + if (visibilityRatio >= SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR) { + result.visibilityVotes[componentId]++; + + // Debug output for specific component + for (const auto& comp : result.components) { + if (comp.featureId == componentId && + (comp.name.find("12v4000g03_herhang") != std::string::npos || + comp.name.find("12V4000G03_HERHANG") != std::string::npos)) { + static bool firstTime = true; + if (firstTime) { + try { + SessionInfo sessionInfo = GetSessionInfo(); + if (sessionInfo.is_valid) { + xstring workdir = sessionInfo.session->GetCurrentDirectory(); + std::string workingDir = XStringToString(workdir); + std::string logPath = workingDir + "\\12v4000g03_analysis.txt"; + std::ofstream logFile(logPath, std::ios::out | std::ios::trunc); + if (logFile.is_open()) { + logFile << "=== 12V4000G03_HERHANG.prt Visibility Analysis ===" << std::endl; + logFile << "Component Name: " << comp.name << std::endl; + logFile << "Feature ID: " << comp.featureId << std::endl; + logFile << "\nBounding Box:" << std::endl; + logFile << " Min: (" << comp.worldAABB.minPoint.x << ", " + << comp.worldAABB.minPoint.y << ", " + << comp.worldAABB.minPoint.z << ")" << std::endl; + logFile << " Max: (" << comp.worldAABB.maxPoint.x << ", " + << comp.worldAABB.maxPoint.y << ", " + << comp.worldAABB.maxPoint.z << ")" << std::endl; + Vector3D size = comp.worldAABB.diagonal(); + logFile << " Size: " << size.x << " x " << size.y << " x " << size.z << std::endl; + logFile << "\nGlobal AABB:" << std::endl; + logFile << " Min: (" << result.globalAABB.minPoint.x << ", " + << result.globalAABB.minPoint.y << ", " + << result.globalAABB.minPoint.z << ")" << std::endl; + logFile << " Max: (" << result.globalAABB.maxPoint.x << ", " + << result.globalAABB.maxPoint.y << ", " + << result.globalAABB.maxPoint.z << ")" << std::endl; + logFile << "\nGrid Resolution: " << grid.gridSizeU << " x " << grid.gridSizeV << std::endl; + logFile << "Total Cells: " << totalCells << std::endl; + logFile << "Minimum Visibility Ratio Per Direction: " << SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR << std::endl; + logFile << "\nVisibility per direction:" << std::endl; + logFile.close(); + } + } + firstTime = false; + } catch (...) {} + } + + // Append visibility data for each direction + try { + SessionInfo sessionInfo = GetSessionInfo(); + if (sessionInfo.is_valid) { + xstring workdir = sessionInfo.session->GetCurrentDirectory(); + std::string workingDir = XStringToString(workdir); + std::string logPath = workingDir + "\\12v4000g03_analysis.txt"; + std::ofstream logFile(logPath, std::ios::out | std::ios::app); + if (logFile.is_open()) { + static int directionCount = 0; + directionCount++; + logFile << "Direction " << directionCount << ": cells=" << cellCount + << ", ratio=" << visibilityRatio + << ", voted=" << (visibilityRatio >= SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR ? "YES" : "NO") + << std::endl; + logFile.close(); + } + } + } catch (...) {} + } + } } } } diff --git a/CreoManager.h b/CreoManager.h index 8a97f00..04cb6d0 100644 --- a/CreoManager.h +++ b/CreoManager.h @@ -27,6 +27,15 @@ #include #include #include +#include + +// Prevent Windows macro conflicts with std::min/max +#ifdef max +#undef max +#endif +#ifdef min +#undef min +#endif // Creo状态信息结构 struct CreoStatus { @@ -595,6 +604,155 @@ private: double getDiagonalLength() const { return diagonal().length(); } + + // Get all 8 corners of AABB + std::vector getCorners() const { + std::vector corners; + corners.reserve(8); + for (int i = 0; i < 8; i++) { + corners.push_back(Vector3D( + (i & 1) ? maxPoint.x : minPoint.x, + (i & 2) ? maxPoint.y : minPoint.y, + (i & 4) ? maxPoint.z : minPoint.z + )); + } + return corners; + } + }; + + // Forward declarations + struct OBB; + + // 2D Screen Grid for visibility calculation + struct ScreenGrid { + struct Cell { + double depth = std::numeric_limits::max(); + int componentId = -1; + }; + + Vector3D origin; // Grid origin point + Vector3D uAxis, vAxis; // Orthonormal basis vectors + double width, height; // Grid dimensions + int gridSizeU, gridSizeV; + std::vector> cells; + + ScreenGrid() : gridSizeU(0), gridSizeV(0), width(0), height(0) {} + + ScreenGrid(const AABB& sceneBounds, const Vector3D& viewDir, int gridSize = 96) { + gridSizeU = gridSize; + gridSizeV = gridSize; + + // Build orthonormal basis + if (std::abs(viewDir.x) > 0.9) { + uAxis = Vector3D(0, 1, 0).cross(viewDir).normalize(); + } else { + uAxis = Vector3D(1, 0, 0).cross(viewDir).normalize(); + } + vAxis = viewDir.cross(uAxis).normalize(); + + // Calculate grid bounds + std::vector corners = sceneBounds.getCorners(); + double minU = std::numeric_limits::max(); + double maxU = std::numeric_limits::lowest(); + double minV = std::numeric_limits::max(); + double maxV = std::numeric_limits::lowest(); + + for (const auto& corner : corners) { + double u = corner.dot(uAxis); + double v = corner.dot(vAxis); + minU = std::min(minU, u); + maxU = std::max(maxU, u); + minV = std::min(minV, v); + maxV = std::max(maxV, v); + } + + // Set origin and dimensions + origin = Vector3D(minU, minV, 0); + width = maxU - minU; + height = maxV - minV; + + // Initialize cells + cells.resize(gridSizeU, std::vector(gridSizeV)); + } + + void updateCell(int u, int v, double depth, int id) { + if (u >= 0 && u < gridSizeU && v >= 0 && v < gridSizeV) { + if (depth < cells[u][v].depth) { + cells[u][v].depth = depth; + cells[u][v].componentId = id; + } + } + } + + std::pair worldToGrid(const Vector3D& point) const { + double u = point.dot(uAxis) - origin.x; + double v = point.dot(vAxis) - origin.y; + int gridU = static_cast((u / width) * gridSizeU); + int gridV = static_cast((v / height) * gridSizeV); + return {gridU, gridV}; + } + + void projectAABB(const AABB& box, const Vector3D& viewDir, double minDepth, int componentId) { + std::vector corners = box.getCorners(); + + // Find UV bounds of projection + int minGridU = gridSizeU, maxGridU = -1; + int minGridV = gridSizeV, maxGridV = -1; + + for (const auto& corner : corners) { + std::pair gridPos = worldToGrid(corner); + int u = gridPos.first; + int v = gridPos.second; + minGridU = std::min(minGridU, std::max(0, u)); + maxGridU = std::max(maxGridU, std::min(gridSizeU - 1, u)); + minGridV = std::min(minGridV, std::max(0, v)); + maxGridV = std::max(maxGridV, std::min(gridSizeV - 1, v)); + } + + // Update cells in bounding rectangle + for (int u = minGridU; u <= maxGridU; u++) { + for (int v = minGridV; v <= maxGridV; v++) { + updateCell(u, v, minDepth, componentId); + } + } + } + + void projectOBB(const OBB& box, const Vector3D& viewDir, double minDepth, int componentId) { + std::vector corners = box.getCorners(); + + // Find UV bounds of projection + int minGridU = gridSizeU, maxGridU = -1; + int minGridV = gridSizeV, maxGridV = -1; + + for (const auto& corner : corners) { + std::pair gridPos = worldToGrid(corner); + int u = gridPos.first; + int v = gridPos.second; + minGridU = std::min(minGridU, std::max(0, u)); + maxGridU = std::max(maxGridU, std::min(gridSizeU - 1, u)); + minGridV = std::min(minGridV, std::max(0, v)); + maxGridV = std::max(maxGridV, std::min(gridSizeV - 1, v)); + } + + // Update cells in bounding rectangle + for (int u = minGridU; u <= maxGridU; u++) { + for (int v = minGridV; v <= maxGridV; v++) { + updateCell(u, v, minDepth, componentId); + } + } + } + + std::unordered_map getVisibilityCount() const { + std::unordered_map counts; + for (const auto& row : cells) { + for (const auto& cell : row) { + if (cell.componentId >= 0) { + counts[cell.componentId]++; + } + } + } + return counts; + } }; // Oriented Bounding Box for enhanced precision @@ -666,6 +824,7 @@ private: std::unordered_set outerComponentIds; // outer component IDs std::vector components; // all components with AABB data AABB globalAABB; // global assembly bounding box + std::unordered_map visibilityRatios; // component ID -> continuous visibility ratio // OBB optimization statistics int obb_usage_count = 0;