Skip to content

Commit

Permalink
Introduce numpy UV triangulation (#1185)
Browse files Browse the repository at this point in the history
  • Loading branch information
Grantim authored Apr 27, 2023
1 parent 43fd806 commit 558e0e3
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 1 deletion.
140 changes: 139 additions & 1 deletion source/mrmeshnumpy/MRPythonNumpyExposing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,146 @@ MR::Mesh fromFV( const pybind11::buffer& faces, const pybind11::buffer& verts )
return res;
}

MR_ADD_PYTHON_FUNCTION( mrmeshnumpy, meshFromFacesVerts, &fromFV, "constructs mesh from given numpy ndarrays of faces (N VertId x3, FaceId x1), verts (M vec3 x3)" )
MR::Mesh fromUVPoints( const pybind11::buffer& xArray, const pybind11::buffer& yArray, const pybind11::buffer& zArray )
{
pybind11::buffer_info xInfo = xArray.request();
pybind11::buffer_info yInfo = yArray.request();
pybind11::buffer_info zInfo = zArray.request();

MR::Vector2i shape;
int format = -1; // 0 - float, 1 - double
auto checkArray = [&] ( const pybind11::buffer_info& info, const std::string& arrayName )->bool
{
if ( info.ndim != 2 )
{
std::string error = arrayName + " should be 2D";
PyErr_SetString( PyExc_RuntimeError, error.c_str() );
return false;
}
MR::Vector2i thisShape;
thisShape.x = int( info.shape[0] );
thisShape.y = int( info.shape[1] );
if ( shape == MR::Vector2i() )
shape = thisShape;
else if ( shape != thisShape )
{
std::string error = "Input arrays shapes should be same";
PyErr_SetString( PyExc_RuntimeError, error.c_str() );
return false;
}
int thisFormat = -1;
if ( info.format == pybind11::format_descriptor<float>::format() )
thisFormat = 0;
else if ( info.format == pybind11::format_descriptor<double>::format() )
thisFormat = 1;

if ( format == -1 )
format = thisFormat;

if ( format == -1 )
{
std::string error = arrayName + " dtype should be float32 or float64";
PyErr_SetString( PyExc_RuntimeError, error.c_str() );
return false;
}
if ( format != thisFormat )
{
std::string error = "Arrays should have same dtype";
PyErr_SetString( PyExc_RuntimeError, error.c_str() );
return false;
}
return true;
};

if ( !checkArray( xInfo, "X" ) || !checkArray( yInfo, "Y" ) || !checkArray( zInfo, "Z" ) )
return {};

assert( format != -1 );
float* fDataPtr[3];
double* dDataPtr[3];
std::function<float( int coord, int i )> getter;
if ( format == 0 )
{
fDataPtr[0] = reinterpret_cast< float* >( xInfo.ptr );
fDataPtr[1] = reinterpret_cast< float* >( yInfo.ptr );
fDataPtr[2] = reinterpret_cast< float* >( zInfo.ptr );
getter = [&] ( int coord, int i )->float
{
int u = i % shape.x;
int v = i / shape.x;
int ind = u * shape.y + v;
return fDataPtr[coord][ind];
};
}
else
{
dDataPtr[0] = reinterpret_cast< double* >( xInfo.ptr );
dDataPtr[1] = reinterpret_cast< double* >( yInfo.ptr );
dDataPtr[2] = reinterpret_cast< double* >( zInfo.ptr );
getter = [&] ( int coord, int i )->float
{
int u = i % shape.x;
int v = i / shape.x;
int ind = u * shape.y + v;
return float( dDataPtr[coord][ind] );
};
}

MR::Mesh res;
res.points.resize( shape.x * shape.y );
tbb::parallel_for( tbb::blocked_range<int>( 0, int( res.points.size() ) ),
[&] (const tbb::blocked_range<int>& range )
{
for ( int i = range.begin(); i < range.end(); ++i )
{
res.points[MR::VertId( i )] = MR::Vector3f( getter( 0, i ), getter( 1, i ), getter( 2, i ) );
}
} );

int triangleCount = 2 * int( res.points.size() ) - 2 * shape.x;
MR::Triangulation t;
t.reserve( triangleCount );

for ( int i = 0; i < shape.y; ++i )
{
for ( int j = 0; j < shape.x; ++j )
{
if ( i + 1 < shape.y && j + 1 < shape.x )
{
t.push_back( {
MR::VertId( i * shape.x + j ),
MR::VertId( i * shape.x + ( j + 1 ) ),
MR::VertId( ( i + 1 ) * shape.x + j ) } );
}
if ( i > 0 && j > 0 )
{
t.push_back( {
MR::VertId( i * shape.x + j ),
MR::VertId( i * shape.x + ( j - 1 ) ),
MR::VertId( ( i - 1 ) * shape.x + j ) } );
}
}
}

res.topology = MR::MeshBuilder::fromTriangles( t );

MR::MeshBuilder::uniteCloseVertices( res, std::numeric_limits<float>::epsilon() );

if ( res.volume() < 0.0f )
res.topology.flipOrientation();

res.pack();

return res;
}

MR_ADD_PYTHON_CUSTOM_DEF( mrmeshnumpy, NumpyMesh, [] ( pybind11::module_& m )
{
m.def( "meshFromFacesVerts", &fromFV, pybind11::arg( "faces" ), pybind11::arg( "verts" ),
"constructs mesh from given numpy ndarrays of faces (N VertId x3, FaceId x1), verts (M vec3 x3)" );
m.def( "meshFromUVPoints", &fromUVPoints, pybind11::arg( "xArray" ), pybind11::arg( "yArray" ), pybind11::arg( "zArray" ),
"constructs mesh from three 2d numpy ndarrays with x,y,z positions of mesh" );
} )

MR::PointCloud pointCloudFromNP( const pybind11::buffer& points, const pybind11::buffer& normals )
{
Expand Down
32 changes: 32 additions & 0 deletions test_python/test_numpy_UVTriangulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from helper import *
from meshlib import mrmeshnumpy
import numpy as np
import unittest as ut
import pytest

# mrmesh uses float32 for vertex coordinates
# however, you could also use float64
def test_numpy_UVSphere():
u, v = np.mgrid[0:2 * np.pi:50j, 0:np.pi:100j]
x = np.cos(u) * np.sin(v)
y = np.sin(u) * np.sin(v)
z = np.cos(v)

mesh = mrmeshnumpy.meshFromUVPoints(x,y,z)
assert (mesh.topology.findHoleRepresentiveEdges().size() == 0)
assert (mrmesh.getAllComponents(mesh).size() == 1)
assert (mesh.volume() > 0)

def test_numpy_UVTorus():
r1 = 2.0
r2 = 1.5

u, v = np.mgrid[0:2 * np.pi:4j, 0:2*np.pi:10j]
x = ( r1 - r2 * np.cos( u ) ) * np.cos( v );
y = ( r1 - r2 * np.cos( u ) ) * np.sin( v )
z = r2 * np.sin( u );

mesh = mrmeshnumpy.meshFromUVPoints(x,y,z)
assert (mesh.topology.findHoleRepresentiveEdges().size() == 0)
assert (mrmesh.getAllComponents(mesh).size() == 1)
assert (mesh.volume() > 0)

0 comments on commit 558e0e3

Please sign in to comment.