-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interactive Contour Labels #1442
Comments
Hello! First of all thank you for sharing your code and the article. If I have understood your casea correctly you have following pipeline:
Most likely second step is the slowest one, also our experience with geometry shaders shows that using it is quite slow too (compared to rendering without geometry shader) Suggestion: MeshLib/source/MRMesh/MRExtractIsolines.h Lines 15 to 16 in 5b62b6d
It can cover steps 1-2 Newer versions of OpenGl have Storage Buffer, I think it can be used to somehow eliminate 2nd step, but it will require a lot of research and OpenGl coding, and I am not sure that it will work as expected. |
Thanks for your prompt response! I'll definitely look into You correctly understand what I'm doing. Step 2 is not the bottleneck here, I am using Transform Feedback and it takes 1-2 ms to complete. The slow part is Step 3 where I'm trying to identify the line segments which belong to the same contour line (have equal Z values) and filter them based on distance to other labels. |
I have trouble understanding how to work with struct RawPolylineArrays {
float* ContourVertices;
int* ContourLengths;
int ArrayLength;
};
PINVOKE RawPolylineArrays CreateContours( Mesh* mesh, float interval)
{
const MeshPart m = MeshPart( *mesh, nullptr );
Plane3f plane;
std::vector<float> contourCoordinates;
std::vector<int> contourLengths;
Box3f box = mesh->getBoundingBox();
float bottom = box.min.z + interval;
float contoursCount = (box.max.z - bottom) / interval;
for ( size_t i = 0; i < contoursCount; i++ )
{
plane = Plane3f( Vector3f::plusZ(), bottom + (i * interval) );
PlaneSections contours = extractPlaneSections( m, plane );
for ( const SurfacePath& contour : contours )
{
for ( const MeshEdgePoint& point : contour )
{
Vector3f& pt = mesh->points[mesh->topology.org( point.e )];
contourCoordinates.push_back( pt.x );
contourCoordinates.push_back( pt.y );
contourCoordinates.push_back( pt.z );
}
contourLengths.push_back( static_cast<int>(contour.size() * 3));
}
}
RawPolylineArrays result = RawPolylineArrays();
result.ArrayLength = static_cast< int >( contourLengths.size());
result.ContourLengths = contourLengths.data();
result.ContourVertices = contourCoordinates.data();
return result;
} |
This block should be changed for ( const MeshEdgePoint& point : contour )
{
--- Vector3f& pt = mesh->points[mesh->topology.org( point.e )];
--- contourCoordinates.push_back( pt.x );
--- contourCoordinates.push_back( pt.y );
--- contourCoordinates.push_back( pt.z );
+++ contourCoordinates.push_back( mesh->edgePoint( point ) )
} Mesh edge point is internal point on edge, ant not its origin using MeshEdgePoint = EdgePoint; MeshLib/source/MRMesh/MREdgePoint.h Lines 8 to 12 in 5b62b6d
|
Thanks, this helped solve the issues! It works well now but can't beat the GLSL code on speed. In the top-left corner you can see the measured time in ms. My GLSL routine takes 2-3 ms even for very low interval values. You mentioned there is potential to speed this logic up, how far do you think this could be pushed? 2023-07-19.12-16-13.mp4 |
First I would recommend to find sections in parallel Box3f box = mesh->getBoundingBox();
float bottom = box.min.z + interval;
float contoursCount = (box.max.z - bottom) / interval;
std::vector<PlaneSections> sections(contoursCount);
ParalleFor( sections, [&]( size_t i )
{
plane = Plane3f( Vector3f::plusZ(), bottom + (i * interval) );
sections[i] = extractPlaneSections( m, plane );
} );
size_t coordsSize = 0;
size_t lengthSize = 0;
for ( const auto& s : sections )
{
lengthSize += s.size();
for ( const auto& path : s )
coordsSize += path.size() * 3;
}
std::vector<float> contourCoordinates;
contourCoordinates.reserve( coordsSize );
std::vector<int> contourLengths;
contourLengths.reserve( lengthSize );
for ( size_t i = 0; i < contoursCount; i++ )
{
for ( const SurfacePath& contour : contours )
{
for ( const MeshEdgePoint& point : contour )
{
Vector3f& pt = mesh->points[mesh->topology.org( point.e )];
contourCoordinates.push_back( pt.x );
contourCoordinates.push_back( pt.y );
contourCoordinates.push_back( pt.z );
}
contourLengths.push_back( static_cast<int>(contour.size() * 3));
}
} I think it should inprove performace, also we can improve performance of single |
Something is wrong with the above code. I commented everything out except for this part and it still results in a memory leak and a crash: Box3f box = mesh->getBoundingBox();
float bottom = box.min.z + interval;
float contoursCount = (box.max.z - bottom) / interval;
std::vector<PlaneSections> sections(contoursCount);
ParallelFor( sections, [&]( size_t i )
{
plane = Plane3f( Vector3f::plusZ(), bottom + (i * interval) );
sections[i] = extractPlaneSections( m, plane );
} ); |
Oh sure, there is race for Box3f box = mesh->getBoundingBox();
float bottom = box.min.z + interval;
float contoursCount = (box.max.z - bottom) / interval;
std::vector<PlaneSections> sections(contoursCount);
ParallelFor( sections, [&]( size_t i )
{
auto localPlane = Plane3f( Vector3f::plusZ(), bottom + (i * interval) );
sections[i] = extractPlaneSections( m, localPlane );
} ); |
Thanks @Grantim, switching to parallel execution speeds things up by 5-8x on my machine: 2023-07-19.14-20-24.mp4I totally agree that this is a more robust way of working with labels but ideally it would be even faster than this. For my application, contour lines are 'just' an analysis mode running on top of other mesh processing operations. The overhead should be as minimal as possible. For reference, there were some minor bugs in the code, I'm posting a working version below. I also removed the size allocation to vectors, there is no measurable difference in performance but the code looks cleaner: const MeshPart m = MeshPart( *mesh, nullptr );
Box3f box = mesh->getBoundingBox();
float bottom = std::ceil( box.min.z / interval ) * interval;
float contoursCount = (box.max.z - bottom) / interval;
std::vector<PlaneSections> sections( (size_t)contoursCount);
ParallelFor( sections, [&] ( size_t i )
{
Plane3f plane = Plane3f( Vector3f::plusZ(), bottom + ( i * interval ) );
sections[i] = extractPlaneSections( m, plane );
} );
std::vector<float> contourCoordinates;
std::vector<int> contourLengths;
for ( const PlaneSections& contours : sections )
{
for ( const SurfacePath& contour : contours )
{
for ( const MeshEdgePoint& point : contour )
{
Vector3f pt = mesh->edgePoint( point );
contourCoordinates.push_back( pt.x );
contourCoordinates.push_back( pt.y );
contourCoordinates.push_back( pt.z );
}
contourLengths.push_back( ( uint32_t )( contour.size() * 3 ) );
}
} |
Thanks, we will work on |
Independently of speeding things up, do you have any suggestions on how to generate labels for each contour line? There will be a user provided |
I think it also should be done in parallel for each section:
|
I'm not sure I understand this. How should I accommodate for the |
I think simple adding segments should be fine, unfortunately I don't have any other idea now. There is decimation algorithm for polylines, but casting it to polyline and decimation will most likely take more time. |
We have improved |
I'll investigate and will keep you posted. On another note, I switched to writing the results directly to the output arrays and parallelized this as well. This shaved off approx. 10-20%. PINVOKE RawPolylineArrays CreateContours( Mesh* mesh, float interval )
{
const MeshPart m = MeshPart( *mesh, nullptr );
const Box3f box = mesh->getBoundingBox();
const float bottom = std::ceil( box.min.z / interval ) * interval;
const float contoursCount = ( box.max.z - bottom ) / interval;
std::vector<PlaneSections> sections( ( size_t )contoursCount );
ParallelFor( sections, [&] ( size_t i )
{
Plane3f plane = Plane3f( Vector3f::plusZ(), bottom + ( i * interval ) );
sections[i] = extractPlaneSections( m, plane );
} );
size_t lengthSize = 0;
size_t coordsSize = 0;
std::vector<size_t> lengthLookup;
std::vector<size_t> coordsLookup;
for ( const auto& s : sections )
{
lengthLookup.emplace_back( lengthSize );
coordsLookup.emplace_back( coordsSize );
lengthSize += s.size();
for ( const auto& path : s )
coordsSize += path.size() * 3;
}
RawPolylineArrays result = RawPolylineArrays();
result.ArrayLength = ( uint32_t )lengthSize;
result.ContourLengths = new int[result.ArrayLength];
result.ContourVertices = new float[coordsSize];
ParallelFor( sections, [&] ( size_t i )
{
size_t localCoordsStart = coordsLookup[i];
size_t localLengthStart = lengthLookup[i];
size_t j = 0;
size_t k = 0;
for ( const SurfacePath& contour : sections[i] )
{
for ( const MeshEdgePoint& point : contour )
{
Vector3f pt = mesh->edgePoint( point );
result.ContourVertices[localCoordsStart + j++] = pt.x;
result.ContourVertices[localCoordsStart + j++] = pt.y;
result.ContourVertices[localCoordsStart + j++] = pt.z;
}
result.ContourLengths[localLengthStart + k++] = ( uint32_t )( contour.size() * 3 );
}
} );
return result;
} |
It's roughly 2.5 times faster now! Super cool! For my test mesh and 0.1 interval I'm down to 4ms (screen capture slows it down a bit): 2023-07-19.17-54-56.mp4Next step is to add the labels. |
Following your suggestion @Grantim, I have implemented a first prototype of contour label display. It has a very minimal overhead and works as expected: 2023-07-19.22-47-46.mp4Code below. I'd appreciate if you could have a look and suggest any optimizations. struct RawPolylineArrays {
float* ContourVertices;
int* ContourVerticesLengths;
int ContourCount;
float* LabelVertices;
float* LabelNormals;
int LabelCount;
};
PINVOKE RawPolylineArrays CreateContours( Mesh* mesh, float interval, bool showLabels, float spacing)
{
const MeshPart m = MeshPart( *mesh, nullptr );
const Box3f box = mesh->getBoundingBox();
const float bottom = std::ceil( box.min.z / interval ) * interval;
const float contoursCount = ( box.max.z - bottom ) / interval;
std::vector<PlaneSections> sections( ( size_t )contoursCount );
ParallelFor( sections, [&] ( size_t i )
{
Plane3f plane = Plane3f( Vector3f::plusZ(), bottom + ( i * interval ) );
sections[i] = extractPlaneSections( m, plane );
} );
size_t lengthSize = 0;
size_t coordsSize = 0;
std::vector<size_t> lengthLookup;
std::vector<size_t> coordsLookup;
for ( const auto& s : sections )
{
lengthLookup.emplace_back( lengthSize );
coordsLookup.emplace_back( coordsSize );
lengthSize += s.size();
for ( const auto& path : s )
coordsSize += path.size() * 3;
}
RawPolylineArrays result = RawPolylineArrays();
result.ContourCount = ( uint32_t )lengthSize;
result.ContourVerticesLengths = new int[result.ContourCount];
result.ContourVertices = new float[coordsSize];
std::vector<std::vector<float>> labelPositions( sections.size() );
std::vector<std::vector<float>> labelNormals( sections.size() );
ParallelFor( sections, [&] ( size_t i )
{
size_t localCoordsStart = coordsLookup[i];
size_t localLengthStart = lengthLookup[i];
size_t j = 0;
size_t k = 0;
std::vector<float> contourLabels;
std::vector<float> contourLabelNormals;
for ( const SurfacePath& contour : sections[i] )
{
float cumulativeDist = 0;
// Add first point manually to avoid if statements in the loop
Vector3f pt = mesh->edgePoint( contour[0] );
result.ContourVertices[localCoordsStart + j++] = pt.x;
result.ContourVertices[localCoordsStart + j++] = pt.y;
result.ContourVertices[localCoordsStart + j++] = pt.z;
for ( size_t l = 1; l < contour.size(); l++ )
{
pt = mesh->edgePoint( contour[l] );
result.ContourVertices[localCoordsStart + j++] = pt.x;
result.ContourVertices[localCoordsStart + j++] = pt.y;
result.ContourVertices[localCoordsStart + j++] = pt.z;
if ( !showLabels )
continue;
Vector3f diff = pt - mesh->edgePoint( contour[l - 1] );
if ( cumulativeDist + diff.length() >= spacing )
{
contourLabels.emplace_back( pt.x );
contourLabels.emplace_back( pt.y );
contourLabels.emplace_back( pt.z );
MeshTriPoint mtp = MeshTriPoint( contour[l] );
Vector3f normal = mesh->normal( mtp );
contourLabelNormals.emplace_back( normal.x );
contourLabelNormals.emplace_back( normal.y );
contourLabelNormals.emplace_back( normal.z );
cumulativeDist = 0;
}
else
{
cumulativeDist += diff.length();
}
}
result.ContourVerticesLengths[localLengthStart + k++] = ( uint32_t )( contour.size() * 3 );
}
if ( showLabels )
{
labelPositions[i] = contourLabels;
labelNormals[i] = contourLabelNormals;
}
} );
if ( !showLabels )
return result;
size_t labelCount = 0;
for ( const auto& l : labelPositions )
{
labelCount += l.size();
}
result.LabelCount = ( int32_t )labelCount;
result.LabelVertices = new float[result.LabelCount];
result.LabelNormals = new float[result.LabelCount];
size_t k = 0;
for ( size_t i = 0; i < labelPositions.size(); i++ )
{
for ( size_t j = 0; j < labelPositions[i].size(); j++ )
{
result.LabelVertices[k] = labelPositions[i][j];
result.LabelNormals[k++] = labelNormals[i][j];
}
}
return result;
} |
Hello, I have a look at your sample and have ideas for minor changes:
if ( showLabels )
{
--- labelPositions[i] = contourLabels;
--- labelNormals[i] = contourLabelNormals;
+++ labelPositions[i] = std::move( contourLabels );
+++ labelNormals[i] = std::move ( contourLabelNormals );
} or even that way --- std::vector<float> contourLabels;
--- std::vector<float> contourLabelNormals;
+++ auto& contourLabels = labelPositions[i];
+++ auto& contourLabelNormals = labelNormals[i]; --- if ( showLabels )
--- {
--- labelPositions[i] = contourLabels;
--- labelNormals[i] = contourLabelNormals;
--- }
size_t k = 0;
for ( size_t i = 0; i < labelPositions.size(); i++ )
{
auto addition = labelPositions[i].size();
std::move( labelPositions[i].begin(), labelPositions[i].end(), result.LabelVertices[k] );
std::move( labelNormals[i].begin(), labelNormals[i].end(), result.LabelNormals[k] );
k += addition;
} P.S. I am not sure if this changes will have any significant effort on performance, but I think they can. |
I think it is important to understand which part of your code takes most of the time (e.g. profile the code). If the time of section extraction is still dominating, then we can think about more accelerations there. For example, you can make use of the fact that all planes are parallel to OXY. And instead of
I guess it can save some 15% time more. More optimizations inside |
Thanks @Grantim, I ported all of your suggestions except for the last move statement. It doesn't compile for some reason. @Fedr, as you can see below,
Could you please provide a sample on how to use the |
Instead of
please try the following code:
|
We have added new overload of |
Oh, sorry, please add
|
Now it worked. Here are the results:
There is no noticeable difference. If anything it seems to be slightly slower than the previous method. |
I see, ok, we will think what can be optimized more. But it will take a while. |
Today we added one more function in MeshLib:
Please give it a try, it might accelerate contour lines extraction from your terrain, if the conditions in the comment are met ( 1. most plane sections are relatively small and 2. cached AABB tree is typically available). |
Thanks @Fedr. You guys keep delivering awesome quality code! Switching to the new function brings the total execution time down to 1-2ms for my test mesh. This is including finding label positions and copying data to managed code. This is perfect for my application. 2023-07-24.09-00-52.mp4 |
This one is a long shot and not directly related to
MeshLib
so please bear with me while I explain. In my application, I rely on a GLSL shader to draw contour lines interactively. The majority of the logic takes place in the Geometry Shader, which essentially implements this algorithm.It works very fast even for complex meshes and I'm happily sharing the code below if you wanted to incorporate it into the MeshInspector.
2023-07-18.16-17-50.mp4
Things slow down, however, when I start displaying labels. The GLSL shader, is only aware of one primitive (triangle) at a time and generates unconnected line segments. I couldn't figure out a way of joining these segments on the GPU, hence I copy them over to the CPU and sort them there. It gets me the results I want, but leads to noticeable slow downs with more complex meshes.
Do you have any suggestions on how to approach it in a more efficient way?
The GLSL shader code:
The c# code taking an array of
float
containing vertex coordinates and normal vectors of respective start & end points of each line segment. Here is the layout, whereV1
is a vertex, andN1
its corresponding normal vector.The text was updated successfully, but these errors were encountered: