Skip to content

Commit

Permalink
Merge pull request #1444 from ptc-lexvandersluijs/feature_alphawrap
Browse files Browse the repository at this point in the history
Feature: new Mesh filter plugin that provides the 3D Alpha Wrap algorithm from CGAL
  • Loading branch information
alemuntoni authored Dec 7, 2023
2 parents 9213493 + 40d6386 commit 44707d9
Show file tree
Hide file tree
Showing 6 changed files with 417 additions and 8 deletions.
4 changes: 2 additions & 2 deletions scripts/Linux/0_setup_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
# Run this script if you never installed any of the MeshLab dependencies.

DONT_INSTALL_QT=false
DONT_INSTALL_CGAL_BOOST=false
DONT_INSTALL_EMBREE=false
DONT_INSTALL_CGAL_BOOST=true
DONT_INSTALL_EMBREE=true

#checking for parameters
for i in "$@"
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ if(NOT DEFINED MESHLAB_PLUGINS) # it may be already defined in parent directory
meshlabplugins/filter_layer
meshlabplugins/filter_measure
meshlabplugins/filter_mesh_booleans
meshlabplugins/filter_mesh_alpha_wrap
meshlabplugins/filter_meshing
meshlabplugins/filter_mls
meshlabplugins/filter_mutualglobal
Expand Down
14 changes: 8 additions & 6 deletions src/external/cgal.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ if(MESHLAB_ALLOW_SYSTEM_CGAL AND TARGET CGAL::CGAL)
add_library(external-cgal INTERFACE)
target_link_libraries(external-cgal INTERFACE CGAL::CGAL Threads::Threads)
elseif(MESHLAB_ALLOW_DOWNLOAD_SOURCE_CGAL)
set(CGAL_DIR "${MESHLAB_EXTERNAL_DOWNLOAD_DIR}/CGAL-5.2.1")
set(CGAL_DIR "${MESHLAB_EXTERNAL_DOWNLOAD_DIR}/CGAL-5.6")
set(CGAL_CHECK "${CGAL_DIR}/include/CGAL/version.h")
set(CGAL_WIN_CHECK "${CGAL_DIR}/auxiliary/gmp/lib/libmpfr-4.lib")

set(CGAL_DOWNLOAD_SUCCESS TRUE)

if (NOT EXISTS ${CGAL_CHECK})
set(CGAL_LINK
https://github.com/CGAL/cgal/releases/download/v5.2.1/CGAL-5.2.1.zip
https://www.meshlab.net/data/libs/CGAL-5.2.1.zip)
set(CGAL_MD5 1a999ab90ea4cf28f6aa17ea2af24876)
https://github.com/CGAL/cgal/releases/download/v5.6/CGAL-5.6.zip
# https://www.meshlab.net/data/libs/CGAL-5.6.zip # Note by LvdS: please host this file on the MeshLab site as well, then remove this comment
)
set(CGAL_MD5 6d1d067b88e20f7080d07d5108b4c772)
download_and_unzip(
NAME "CGAL"
LINK ${CGAL_LINK}
Expand All @@ -37,8 +38,9 @@ elseif(MESHLAB_ALLOW_DOWNLOAD_SOURCE_CGAL)

if (WIN32 AND NOT EXISTS ${CGAL_WIN_CHECK})
set(CGAL_AUX_LINK
https://github.com/CGAL/cgal/releases/download/v5.2.1/CGAL-5.2.1-win64-auxiliary-libraries-gmp-mpfr.zip
https://www.meshlab.net/data/libs/CGAL-5.2.1-win64-auxiliary-libraries-gmp-mpfr.zip)
https://github.com/CGAL/cgal/releases/download/v5.6/CGAL-5.6-win64-auxiliary-libraries-gmp-mpfr.zip
# https://www.meshlab.net/data/libs/CGAL-5.6-win64-auxiliary-libraries-gmp-mpfr.zip # Note by LvdS: please host this file on the MeshLab site as well, then remove this comment
)
set(CGAL_AUX_MD5 247f4dca741c6b9a9be76286414070fa)
download_and_unzip(
NAME "CGAL auxiliary libraries"
Expand Down
14 changes: 14 additions & 0 deletions src/meshlabplugins/filter_mesh_alpha_wrap/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2023, PTC

if (TARGET external-boost AND TARGET external-cgal AND TARGET external-libigl)
set(SOURCES filter_mesh_alpha_wrap.cpp)

set(HEADERS filter_mesh_alpha_wrap.h)

add_meshlab_plugin(filter_mesh_alpha_wrap ${SOURCES} ${HEADERS})

target_link_libraries(filter_mesh_alpha_wrap PRIVATE external-boost external-cgal external-libigl)
else()
message(
STATUS "Skipping filter_mesh_alpha_wrap - don't know about boost, cgal or libigl on this system.")
endif()
319 changes: 319 additions & 0 deletions src/meshlabplugins/filter_mesh_alpha_wrap/filter_mesh_alpha_wrap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
/*****************************************************************************
* MeshLab - Alpha Wrap plugin o o *
* by PTC, based on CGAL o o *
* _ O _ *
* Copyright(C) 2023 \/)\/ *
* PTC /\/| *
* Lex van der Sluijs | *
* \ *
* All rights reserved. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) *
* for more details. *
* *
****************************************************************************/

#include "filter_mesh_alpha_wrap.h"

#include <common/utilities/eigen_mesh_conversions.h>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h>
#include <CGAL/Surface_mesh.h>
#include "CGAL/alpha_wrap_3.h"
#include "qdebug.h"


namespace PMP = CGAL::Polygon_mesh_processing;
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Point_3 = K::Point_3;
using CgalMesh = CGAL::Surface_mesh<Point_3>;

typedef CgalMesh::Vertex_index vertex_descriptor;
typedef CgalMesh::Face_index face_descriptor;

typedef boost::graph_traits<CgalMesh>::vertex_descriptor boost_vertex_descriptor;

/**
* @brief
* Constructor usually performs only two simple tasks of filling the two lists
* - typeList: with all the possible id of the filtering actions
* - actionList with the corresponding actions.
* If you want to add icons to your filtering actions you can do here by construction the QActions
* accordingly
*/
FilterMeshAlphaWrap::FilterMeshAlphaWrap()
{
typeList = {MESH_ALPHA_WRAP};

for (const ActionIDType& tt : typeList)
actionList.push_back(new QAction(filterName(tt), this));
}

QString FilterMeshAlphaWrap::pluginName() const
{
return "FilterMeshAlphaWrap";
}

QString FilterMeshAlphaWrap::vendor() const
{
return "PTC";
}

/**
* @brief ST() must return the very short string describing each filtering action
* (this string is used also to define the menu entry)
* @param filterId: the id of the filter
* @return the name of the filter
*/
QString FilterMeshAlphaWrap::filterName(ActionIDType filterId) const
{
switch (filterId) {
case MESH_ALPHA_WRAP: return "Alpha Wrap";
default: assert(0); return QString();
}
}

QString FilterMeshAlphaWrap::pythonFilterName(ActionIDType f) const
{
switch (f) {
case MESH_ALPHA_WRAP: return "generate_alpha_wrap";
default: assert(0); return QString();
}
}

/**
* @brief // Info() must return the longer string describing each filtering action
* (this string is used in the About plugin dialog)
* @param filterId: the id of the filter
* @return an info string of the filter
*/
QString FilterMeshAlphaWrap::filterInfo(ActionIDType filterId) const
{
QString description =
"This filter extecutes an Alpha Wrap based on the input mesh. <br>"
"The filter uses the original code provided in CGAL "
"<a href=\"https://doc.cgal.org/latest/Alpha_wrap_3/index.html#Chapter_3D_Alpha_wrapping\">3D Alpha Wrapping</a>.<br>"
"<br>"
"Alpha: this is the size of the 'ball', specified as the fraction of the length of the largest diagonal of the bounding box. So, if this value is 0.02 then the size of the ball is 2% of the largest diagonal. Note that the run-time and memory consumption will increase with a smaller ball size.<br><br>"
"Offset: the offset distance that is added to the surface, always larger than 0, as a fraction of the length of the largest diagonal. A value of 0.001 means that the surface will be offset by a thousandth of this length.<br><br>"
"The implementation is based on the following paper:<br>"
"Cédric Portaneri, Mael Rouxel-Labbé, Michael Hemmer, David Cohen-Steiner, Pierre Alliez, "
"<a href=\"https://inria.hal.science/hal-03688637\"><b>\"Alpha Wrapping with an Offset\"</b></a> (2022)<br><br>This plugin is contributed by Lex van der Sluijs at PTC.<br>";
switch (filterId) {
case MESH_ALPHA_WRAP: return description;
default: assert(0); return "Unknown Filter";
}
}

/**
* @brief The FilterClass describes in which generic class of filters it fits.
* This choice affect the submenu in which each filter will be placed
* More than a single class can be chosen.
* @param a: the action of the filter
* @return the class od the filter
*/
FilterMeshAlphaWrap::FilterClass FilterMeshAlphaWrap::getClass(const QAction* a) const
{
switch (ID(a)) {
case MESH_ALPHA_WRAP:
return FilterPlugin::FilterClass(
FilterPlugin::FilterClass(FilterPlugin::Layer + FilterPlugin::Remeshing));
default: assert(0); return FilterPlugin::Generic;
}
}

/**
* @brief FilterSamplePlugin::filterArity
* @return
*/
FilterPlugin::FilterArity FilterMeshAlphaWrap::filterArity(const QAction*) const
{
return FIXED;
}

/**
* @brief FilterSamplePlugin::getPreConditions
* @return
*/
// int FilterMeshBooleans::getPreConditions(const QAction*) const
//{
// return MeshModel::MM_NONE;
//}

/**
* @brief FilterSamplePlugin::postCondition
* @return
*/
// int FilterMeshBooleans::postCondition(const QAction*) const
//{
// return MeshModel::MM_VERTCOORD | MeshModel::MM_FACENORMAL | MeshModel::MM_VERTNORMAL;
//}

/**
* @brief This function define the needed parameters for each filter. Return true if the filter has
* some parameters it is called every time, so you can set the default value of parameters according
* to the mesh For each parameter you need to define,
* - the name of the parameter,
* - the default value
* - the string shown in the dialog
* - a possibly long string describing the meaning of that parameter (shown as a popup help in the
* dialog)
* @param action
* @param m
* @param parlst
*/
RichParameterList
FilterMeshAlphaWrap::initParameterList(const QAction* action, const MeshDocument& md)
{
RichParameterList parlst;
switch (ID(action)) {
case MESH_ALPHA_WRAP:
{
parlst.addParam(RichFloat(
"Alpha fraction",
0.02,
tr("Alpha: the size of the ball (fraction)"),
tr("")));
parlst.addParam(RichFloat(
"Offset fraction",
0.001,
tr("Offset added to the surface (fraction)"),
tr("")));
} break;
default: assert(0);
}
return parlst;
}



/**
* @brief The Real Core Function doing the actual mesh processing.
* @param action
* @param md: an object containing all the meshes and rasters of MeshLab
* @param par: the set of parameters of each filter
* @param cb: callback object to tell MeshLab the percentage of execution of the filter
* @return true if the filter has been applied correctly, false otherwise
*/
std::map<std::string, QVariant> FilterMeshAlphaWrap::applyFilter(
const QAction* action,
const RichParameterList& par,
MeshDocument& md,
unsigned int& /*postConditionMask*/,
vcg::CallBackPos*)
{
float alpha_fraction = par.getFloat("Alpha fraction");
float offset_fraction = par.getFloat("Offset fraction");

CgalMesh inputMesh;
CgalMesh wrapResult;

// ------- initialize the CGAL input mesh based on the contents of the MeshDocument ----------
// 'cm' is 'current mesh' or 'compute mesh' ?
CMeshO& m = md.mm()->cm;

// note: coding guided from these pages:
// http://vcglib.net/platonic_8h_source.html to read from VCGlib mesh
// https://doc.cgal.org/latest/Surface_mesh/index.html#Chapter_3D_Surface_mesh to write to CGAL mesh

for (auto vi = m.vert.begin(); vi != m.vert.end(); ++vi){
vertex_descriptor vertex = inputMesh.add_vertex(K::Point_3((*vi).P().X(), (*vi).P().Y(), (*vi).P().Z()));
}

bool meshContainsQuads = false;
for (auto fi = m.face.begin(); fi != m.face.end(); ++fi)
{
vertex_descriptor u( (*fi).V(0)->Index() );
vertex_descriptor v( (*fi).V(1)->Index() );
vertex_descriptor w( (*fi).V(2)->Index() );

//qDebug() << "m.face (*fi).VN() " << (*fi).VN();
if ( (*fi).VN() == 4)
{
meshContainsQuads = true;
vertex_descriptor x( (*fi).V(3)->Index() );
face_descriptor face = inputMesh.add_face(u, v, w, x);
}
else
face_descriptor face = inputMesh.add_face(u, v, w);
}

CGAL::Bbox_3 bbox = CGAL::Polygon_mesh_processing::bbox(inputMesh);
const double diag_length = std::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) +
CGAL::square(bbox.ymax() - bbox.ymin()) +
CGAL::square(bbox.zmax() - bbox.zmin()));

switch (ID(action)) {
case MESH_ALPHA_WRAP:

{
if(meshContainsQuads)
{
qDebug() << "Input mesh contains quads, triangulating first";
CGAL::Polygon_mesh_processing::triangulate_faces(inputMesh);
}

CGAL::alpha_wrap_3(inputMesh, diag_length * alpha_fraction, diag_length * offset_fraction, wrapResult);

// making a nice label that has the used parameter doesn't work, since it will be interpreted
// as a file name and the UI will manipulate the string based on where the dots are.
// QString newName = QString::asprintf("Alpha Wrap a=%.4f, o=%.4f").arg(alpha_fraction, offset_fraction);
QString newName = "Alpha wrap";
MeshModel* mesh = md.addNewMesh("", newName, true);

EigenMatrixX3m VR;
Eigen::MatrixX3i FR;


VR.resize(wrapResult.num_vertices(), Eigen::NoChange);
FR.resize(wrapResult.num_faces(), Eigen::NoChange);

// --------- copy results from wrap into Eigen matrices --------------
// 'rvi' = result vertex iterator, vi = vertex index
int vi = 0;
for(auto rvi = wrapResult.vertices().begin(); rvi != wrapResult.vertices().end(); ++rvi)
{
VR(vi, 0) = wrapResult.point(*rvi).x();
VR(vi, 1) = wrapResult.point(*rvi).y();
VR(vi, 2) = wrapResult.point(*rvi).z();
++vi;
}

int faceIndex = 0;
for(auto rfi = wrapResult.faces().begin(); rfi != wrapResult.faces().end(); ++rfi)
{
// qDebug() << "vertices around face " << *rfi;
CGAL::Vertex_around_face_iterator<CgalMesh> vbegin, vend;
int i = 0;
for(boost::tie(vbegin, vend) = vertices_around_face(wrapResult.halfedge( *rfi), wrapResult); vbegin != vend; ++vbegin)
{
// qDebug() << *vbegin << " vbegin->idx() " << vbegin->idx();
FR(faceIndex, i) = vbegin->idx();
++i;
}

++faceIndex;
}


// ------------- then initialize the new mesh using these Eigen matrices ----------
mesh->cm = meshlab::meshFromMatrices(VR, FR);
}
break;

default: wrongActionCalled(action);
}
return std::map<std::string, QVariant>();
}


MESHLAB_PLUGIN_NAME_EXPORTER(FilterMeshAlphaWrap)
Loading

0 comments on commit 44707d9

Please sign in to comment.