feat: implement 2D grid-based visibility analysis for shell detection

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 <noreply@anthropic.com>
This commit is contained in:
sladro 2025-09-19 22:18:51 +08:00
parent 5291bfc765
commit 0a2804a0f2
2 changed files with 269 additions and 19 deletions

View File

@ -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<ProjectionData> 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<double>::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 (...) {}
}
}
}
}
}

View File

@ -27,6 +27,15 @@
#include <algorithm>
#include <cmath>
#include <utility>
#include <limits>
// 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<Vector3D> getCorners() const {
std::vector<Vector3D> 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<double>::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<std::vector<Cell>> 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<Vector3D> corners = sceneBounds.getCorners();
double minU = std::numeric_limits<double>::max();
double maxU = std::numeric_limits<double>::lowest();
double minV = std::numeric_limits<double>::max();
double maxV = std::numeric_limits<double>::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<Cell>(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<int, int> worldToGrid(const Vector3D& point) const {
double u = point.dot(uAxis) - origin.x;
double v = point.dot(vAxis) - origin.y;
int gridU = static_cast<int>((u / width) * gridSizeU);
int gridV = static_cast<int>((v / height) * gridSizeV);
return {gridU, gridV};
}
void projectAABB(const AABB& box, const Vector3D& viewDir, double minDepth, int componentId) {
std::vector<Vector3D> 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<int, int> 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<Vector3D> 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<int, int> 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<int, int> getVisibilityCount() const {
std::unordered_map<int, int> 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<int> outerComponentIds; // outer component IDs
std::vector<ComponentItem> components; // all components with AABB data
AABB globalAABB; // global assembly bounding box
std::unordered_map<int, double> visibilityRatios; // component ID -> continuous visibility ratio
// OBB optimization statistics
int obb_usage_count = 0;