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:
parent
5291bfc765
commit
0a2804a0f2
129
CreoManager.cpp
129
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<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 (...) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
159
CreoManager.h
159
CreoManager.h
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user