-
Notifications
You must be signed in to change notification settings - Fork 58
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
Creating grid meshes based on arbitrary shape input #2371
Comments
Hello! I believe Distance map functionality is the thing you are looking for also there is function to create mesh without computing whole distance map MeshLib/source/MRMesh/MRRegularGridMesh.h Lines 20 to 24 in 9d16a0e
here |
Thanks @Grantim! Could you please show an example on how to call the makeRegularGridMesh function on an existing mesh or a closed curve? |
Sure: MeshLib/source/MRMesh/MRDistanceMap.h Lines 170 to 173 in 9d16a0e
and then back to mesh: MeshLib/source/MRMesh/MRDistanceMap.h Lines 285 to 286 in 9d16a0e
for 2d curves there is similar function (each distance map value is distance to the curve in plane): MeshLib/source/MRMesh/MRDistanceMap.h Lines 217 to 222 in 9d16a0e
and then back to mesh (same as in first example): MeshLib/source/MRMesh/MRDistanceMap.h Lines 285 to 286 in 9d16a0e
makeRegularGridMesh( resolutionX, resolutionY, [&]( size_t x, size_t y )
{
return !isBlueVertex(x,y); // blue vertices are invalid and should not be present in result mesh
},
[&]( size_t x, size_t y )
{
return originPoint + Vector3f( x * pixelSizeX, y * pixelSizeY, calcVertexHeight( x, y ) );
} );
Also, we had some discussion about distance maps here #1382 |
Super! It was #1382 that I was looking for and One more question, is there a way to limit the output of a My input mesh is a circle, the mask is a closed curve in an arbitrary plane. I'd like the resulting mesh to be only limited to the vertices whose projection to the World.XY plane falls within the outline of the mask. My current approach would be to start with the mask, create a |
I think algorithm here should look like this:
Polyline2 polyline( inputCurve ); MeshLib/source/MRMesh/MRPolyline.h Lines 28 to 29 in 0f80451
MeshToDistanceMapParams params;
params.xRange = Vector3f::plusX() * pixelSize;
params.yRange = Vector3f::plusY() * pixelSize;
auto box = polyline.getBoundingBox();
const int offsetPixels = 2;
params.orgPoint = to3dim( box.min ) - offsetPixels * ( params.xRange + params.yRange ); // 2 pixels offset of minimum point
params.resolution = Vector3i( box.size() / pixelSize ) + ( 2 * offsetPixels + 1 ) * Vector3i::diagonal( 1 ); // + 1 to `ceil` division result
params.direction = Vector3f::plusZ(); // may be Vector3f::minusZ(); as well
auto dm = computeDistanceMap( inputMesh, params ); At this point distance map has all values around input curve
DistanceMapToWorld dmToWorld(params); // params to backproject pixels to world
ParallelFor( 0, int( dm.size() ), [&]( int i )
{
auto intPos = dm.toPos( i );
auto coord = dm.unproject( intPos.x, intPos.y, dmToWorld );
if ( !coord )
return; // pixel value is already invalid
if ( !isPointInsidePolyline( polyline, to2dim( *coord ) )
dm.unset( i );
} );
auto mesh = distanceMapToMesh( dm, dmToWorld ); |
Hey @Grantim, I'm testing the above code and have a few questions still. Could you please help me resolve these?
I'd like to have a gridded mesh conforming to the curve. Similar to how this triangle mesh works, but with a regular grid: I could of course, first create the triangle mesh, and then grid it using the distance map as above but maybe there is a better way. My current code: PINVOKE Mesh* CurveToGridMesh( const float coordinates[], const int coordinatesLength, const float resolution )
{
float res = resolution < 0.01 ? 0.01f : resolution;
std::vector<Vector2f> coordinateVector;
coordinateVector.reserve( coordinatesLength / 3 );
for ( size_t i = 0; i < coordinatesLength; i += 3 )
{
coordinateVector.emplace_back( coordinates[i], coordinates[i + 1] );
}
DecimatePolylineSettings2 dps{ .maxError = MAXERROR };
decimateContour( coordinateVector, dps );
coordinateVector.emplace_back( coordinateVector[0] ); // repeat first vertex since addFromPoints removes it
Polyline2 polyline;
polyline.addFromPoints( coordinateVector.data(), coordinateVector.size() );
auto box = polyline.getBoundingBox();
ContourToDistanceMapParams params;
params.orgPoint = box.min;
params.resolution = Vector2i( static_cast< int >( std::ceil( box.size().x / res) ), static_cast< int >( std::ceil( box.size().y / res) ));
DistanceMap proposedMap = distanceMapFromContours( polyline, params );
Mesh* gridMesh = new Mesh( distanceMapToMesh( proposedMap, DistanceMapToWorld( params ) ) );
return gridMesh;
}
PINVOKE Mesh* ProjectGridToMesh( const Mesh* mesh, const float resolution, const float coordinates[], const int coordinatesLength )
{
float res = resolution < 0.01 ? 0.01f : resolution;
std::vector<Vector2f> coordinateVector;
coordinateVector.reserve( coordinatesLength / 3 );
for ( size_t i = 0; i < coordinatesLength; i += 3 )
{
coordinateVector.emplace_back( coordinates[i], coordinates[i + 1] );
}
DecimatePolylineSettings2 dps{ .maxError = MAXERROR };
decimateContour( coordinateVector, dps );
coordinateVector.emplace_back( coordinateVector[0] ); // repeat first vertex since addFromPoints removes it
Polyline2 polyline;
polyline.addFromPoints( coordinateVector.data(), coordinateVector.size() );
MeshToDistanceMapParams params;
params.xRange = Vector3f::plusX() * res;
params.yRange = Vector3f::plusY() * res;
auto box = polyline.getBoundingBox();
const float offsetPixels = 2;
params.orgPoint = to3dim( box.min ) - offsetPixels * ( params.xRange + params.yRange );
Vector2f r = box.size() / res + ( 2.0f * offsetPixels + 1.0f ) * Vector2f::diagonal( 1 );
params.resolution = Vector2i( static_cast< int > (r.x), static_cast< int > (r.y));
params.direction = Vector3f::plusZ();
auto dm = computeDistanceMap( *mesh, params );
DistanceMapToWorld dmToWorld( params ); // params to backproject pixels to world
ParallelFor( 0, int( dm.size() ), [&] ( int i )
{
auto intPos = dm.toPos( i );
auto coord = dm.unproject( intPos.x, intPos.y, dmToWorld );
if ( !coord )
return; // pixel value is already invalid
if ( !isPointInsidePolyline( polyline, to2dim( *coord ) ))
dm.unset( i );
} );
Mesh* result = new Mesh(distanceMapToMesh( dm, dmToWorld ));
return result;
} |
Hello!
PINVOKE Mesh* ProjectGridToMesh( const Mesh* mesh, const float resolution, const float coordinates[], const int coordinatesLength )
{
float res = resolution < 0.01 ? 0.01f : resolution;
std::vector<Vector2f> coordinateVector;
coordinateVector.reserve( coordinatesLength / 3 );
for ( size_t i = 0; i < coordinatesLength; i += 3 )
{
coordinateVector.emplace_back( coordinates[i], coordinates[i + 1] );
}
coordinateVector.emplace_back( coordinateVector[0] ); // this is needed to understand that contour is closed
// better create polyline before decimating, because decimateContour will create it too if you pass simple contours
Polyline2 polyline( coordinateVector );
DecimatePolylineSettings2 dps{ .maxError = MAXERROR };
decimateContour( polyline, dps );
MeshToDistanceMapParams params;
params.xRange = Vector3f::plusX() * res;
params.yRange = Vector3f::plusY() * res;
auto box = polyline.getBoundingBox();
const float offsetPixels = 2;
params.orgPoint = to3dim( box.min ) - offsetPixels * ( params.xRange + params.yRange );
Vector2f r = box.size() / res + ( 2.0f * offsetPixels + 1.0f ) * Vector2f::diagonal( 1 );
params.resolution = Vector2i( static_cast< int > (r.x), static_cast< int > (r.y));
params.direction = Vector3f::minusZ(); // mb try minusZ
params.orgPoint.z = mesh.getBoundingBox().max.z + offsetPixels; // make sure that origin point is higher than mesh
auto dm = computeDistanceMap( *mesh, params );
DistanceMapToWorld dmToWorld( params ); // params to backproject pixels to world
ParallelFor( 0, int( dm.size() ), [&] ( int i )
{
auto intPos = dm.toPos( i );
auto coord = dm.unproject( intPos.x, intPos.y, dmToWorld );
if ( !coord )
return; // pixel value is already invalid
if ( !isPointInsidePolyline( polyline, to2dim( *coord ) ))
dm.unset( i );
} );
Mesh* result = new Mesh(distanceMapToMesh( dm, dmToWorld ));
return result;
} |
Thanks @Grantim! For 2. I'll triangulate the input contour first and run a distance map on this mesh. Polyline2 doesn't have a constructor which would take a Polyline2 polyline( coordinateVector ); So I replaced it with: Polyline2 polyline;
polyline.addFromPoints( coordinateVector.data(), coordinateVector.size() );
DecimatePolylineSettings2 dps{ .maxError = MAXERROR };
decimatePolyline( polyline, dps ); But it still results in an invalid mesh. Attached the sample data I'm working with |
Oh really, it should look this Polyline2 polyline( { coordinateVector } ); I'll take a look at the data and come back to you |
There is only mesh in the file attached, could you please also provide curve and parameters EDIT: |
OK, this is a super simple polyline with the following vertices:
But you can draw any closed polyline and a resolution of say 1.2 meters. It always results in an invalid mesh, independently of the inputs. |
Thanks @mariuszhermansdorfer ! There was an error in setting up parameters for distance map, it should be like this: MeshToDistanceMapParams params;
auto box = polyline.getBoundingBox();
const float offsetPixels = 2;
box.include( box.min - res * offsetPixels * Vector2f::diagonal( 1 ) );
box.include( box.max + res * offsetPixels * Vector2f::diagonal( 1 ) );
params.orgPoint = to3dim( box.min );
Vector2f r = box.size() / res + Vector2f::diagonal( 1 );
params.xRange = Vector3f::plusX() * box.size().x;
params.yRange = Vector3f::plusY() * box.size().y;
params.resolution = Vector2i( static_cast< int > ( r.x ), static_cast< int > ( r.y ) );
params.direction = Vector3f::minusZ(); // mb try minusZ
params.orgPoint.z = mesh.getBoundingBox().max.z + offsetPixels; // make sure that origin point is higher than mesh
So full code should look like this PINVOKE Mesh* ProjectGridToMesh( const Mesh* mesh, const float resolution, const float coordinates[], const int coordinatesLength )
{
float res = resolution < 0.01 ? 0.01f : resolution;
std::vector<Vector2f> coordinateVector;
coordinateVector.reserve( coordinatesLength / 3 );
for ( size_t i = 0; i < coordinatesLength; i += 3 )
{
coordinateVector.emplace_back( coordinates[i], coordinates[i + 1] );
}
coordinateVector.emplace_back( coordinateVector[0] ); // this is needed to understand that contour is closed
// better create polyline before decimating, because decimateContour will create it too if you pass simple contours
Polyline2 polyline( { coordinateVector } ); // added {} here
DecimatePolylineSettings2 dps{ .maxError = MAXERROR };
decimateContour( polyline, dps );
MeshToDistanceMapParams params;
auto box = polyline.getBoundingBox();
const float offsetPixels = 2;
box.include( box.min - res * offsetPixels * Vector2f::diagonal( 1 ) );
box.include( box.max + res * offsetPixels * Vector2f::diagonal( 1 ) );
params.orgPoint = to3dim( box.min );
Vector2f r = box.size() / res + Vector2f::diagonal( 1 );
params.xRange = Vector3f::plusX() * box.size().x;
params.yRange = Vector3f::plusY() * box.size().y;
params.resolution = Vector2i( static_cast< int > ( r.x ), static_cast< int > ( r.y ) );
params.direction = Vector3f::minusZ(); // mb try minusZ
params.orgPoint.z = mesh.getBoundingBox().max.z + offsetPixels; // make sure that origin point is higher than mesh
auto dm = computeDistanceMap( *mesh, params );
DistanceMapToWorld dmToWorld( params ); // params to backproject pixels to world
ParallelFor( 0, int( dm.size() ), [&] ( int i )
{
auto intPos = dm.toPos( i );
auto coord = dm.unproject( intPos.x, intPos.y, dmToWorld );
if ( !coord )
return; // pixel value is already invalid
if ( !isPointInsidePolyline( polyline, to2dim( *coord ) ))
dm.unset( i );
} );
Mesh* result = new Mesh(distanceMapToMesh( dm, dmToWorld ));
return result;
} |
Thanks @Grantim! Confirmed :) |
Hey @Grantim, I'm revisiting this topic with two, hopefully minor, feature requests:
Could there be an option to use MRCuda-based raycasting? My use cases quite often require 4M+ grid points to be created and DistanceMap creation slows down noticeably.
[EDIT] Would it make sense to voxelize this kind of geometry maybe? Would this be faster or slower than raycasting? |
Hello @mariuszhermansdorfer !
Sure, it can be done, but now we don't have CUDA implementation, we will add it.
It depends on what result you want to achieve, distance map is 2d grid, while voxelization is 3d grid, so in common it is N times slower |
Thanks @Grantim! What I'm trying to achieve is that the points would be regularly distributed on the vertical surface of the cylinder (or any arbitrarily shaped mesh). Something like this but in a regular grid: |
Thanks for explanation! Regular DistanceMap approach will not work here, but you still can create custom DistanceMap, for example in radial coordinates:
|
I see, so in this case I think there is no good way to achieve regular mesh, (because in common case there could be undercuts or some topology issues). Maybe there is another way to solve your problem without creating regular grid mesh? You can try voxelization but I it will output different mesh than auto volume = meshToDistanceVolume(mesh).volume(); // need to setup parameters here especially `maxDistSq = sqr(2*voxelSize)`
auto mesh = marchingCubes(volume ); // also need to setup parameters here |
The intended use case is for sunlight analysis. The surfaces above represent walls of buildings. For each point on the surface, I would perform raycasting with a series of vectors representing sun directions throughout a given day. For each point, I can count how many hours it received direct sunlight and visualize this as a mesh. Creating a regular grid assures a uniform distribution of these sample points. But maybe we could approach this in a different way? Maybe poisson disk sampling, or any other uniform sampling method could work? I tried remeshing with a set edge length, but it was approx. 10x slower than DistanceMap. [Edit] Here is an example using only the ground plane for analysis, I'd like to extend this to building walls as well: 2024-04-05.12-12-40.mp4 |
As I understand You raycast from the plane to the sun, maybe it can be done vice versa? Raycast from the sun to the ground, so you will both get information about sunlight on plane and about sunlight on building, with no need in any regular mesh, will it work? |
Sure, but I still need the targets on the building and then a way of color coding the results (vertex colors of a mesh). |
In this way re-mesh looks like best option here. I think can call it in parallel for all buildings, so it may improve performance |
Super, I'll resort to re-meshing for nonplanar surfaces. Would be cool if you could add the CUDA-based |
Hello again! In new version of meshlib we've added cuda version of |
Thanks @Grantim! It works as expected, but unfortunately it seems that the bottleneck is elsewhere and it's caused by the DistanceMap computeDistanceMap(const Mesh& mesh, const MeshToDistanceMapParams& params, bool useGPU) {
if (MR::Cuda::isCudaAvailable() && useGPU)
return MR::Cuda::computeDistanceMap(mesh, params);
else
return computeDistanceMap(MeshPart(mesh), params);
}
PINVOKE Mesh* GridRemesh( const Mesh* mesh, const float resolution, bool useGPU)
{
float res = std::max(resolution, MIN_RESOLUTION);
const auto& bbox = mesh->getBoundingBox();
auto boxSize = bbox.size();
MeshToDistanceMapParams params;
params.direction = Vector3f( 0, 0, 1 );
params.allowNegativeValues = false;
params.orgPoint = bbox.min;
params.xRange = Vector3f( boxSize.x, 0.f, 0.f );
params.yRange = Vector3f( 0.f, boxSize.y, 0.f );
params.resolution = Vector2i( static_cast< int >( std::ceil( boxSize.x / res ) ), static_cast< int >( std::ceil( boxSize.y / res ) ) );
auto proposedMap = computeDistanceMap(*mesh, params, useGPU);
Mesh* gridMesh = new Mesh( distanceMapToMesh( proposedMap, params ).value() );
return gridMesh;
} |
Oh, I see, in fact we have already optimized this algorithm and I don't think that it can be speed up much more, but we'll have a look at this |
Having looked at the code here:
I have a suggestion which could work in this specific case. Since we know all dimensions of the grid, we can safely assume a regular triangulation pattern (n, n + 1, n * stride). Couldn’t we leverage this knowledge to use this algorithm:
I’m not sure about its speed, but it seems that it could avoid a lot of checks going on in the current logic. |
Old implementation of the function looked like this: |
Hi @Grantim and @Fedr,
I believe to have seen this before but can't find the corresponding function now. How can I create a gridded mesh based on arbitrary shape input, a mask, and a desired spacing value?
For a rectangle it would look like this:
Input shapes would be closed curves or open meshes (both convex and concave). The resulting grid would be trimmed to the outline, such that no vertex lays outside of it:
The grid would be projected from the World.XY plane, even if the input shape is non-planar. To be clear, the resulting vertices would follow the input shape, kind of like this:
Input mesh:
Resulting gridded mesh:
Top view:
I'd appreciate it if you could point me in the right direction!
The text was updated successfully, but these errors were encountered: