Commit 1a0a76dd authored by Jan Möbius's avatar Jan Möbius

Merge branch 'rename_property_manager_factories' into 'master'

rename PropertyManager factory functions

See merge request !193
parents 19bd618a ab12d50e
Pipeline #7967 passed with stages
in 77 minutes and 56 seconds
#include <iostream>
#include <vector>
// --------------------
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
#include <OpenMesh/Core/Utils/PropertyManager.hh>
typedef OpenMesh::TriMesh_ArrayKernelT<> MyMesh;
#include <iostream>
#include <vector>
using MyMesh = OpenMesh::TriMesh_ArrayKernelT<>;
int main(int argc, char **argv)
int main(int argc, char** argv)
{
MyMesh mesh;
// check command line options
if (argc != 4)
{
std::cerr << "Usage: " << argv[0] << " #iterations infile outfile\n";
return 1;
}
// read mesh from stdin
if ( ! OpenMesh::IO::read_mesh(mesh, argv[2]) )
{
std::cerr << "Error: Cannot read mesh from " << argv[2] << std::endl;
return 1;
}
// this vertex property stores the computed centers of gravity
OpenMesh::VPropHandleT<MyMesh::Point> cogs;
mesh.add_property(cogs);
// smoothing mesh argv[1] times
MyMesh::VertexIter v_it, v_end(mesh.vertices_end());
MyMesh::VertexVertexIter vv_it;
MyMesh::Point cog;
MyMesh::Scalar valence;
unsigned int i, N(atoi(argv[1]));
for (i=0; i < N; ++i)
{
for (v_it=mesh.vertices_begin(); v_it!=v_end; ++v_it)
{
mesh.property(cogs,*v_it).vectorize(0.0f);
valence = 0.0;
for (vv_it=mesh.vv_iter( *v_it ); vv_it; ++vv_it)
{
mesh.property(cogs,*v_it) += mesh.point( *vv_it );
++valence;
}
mesh.property(cogs,*v_it) /= valence;
// Read command line options
MyMesh mesh;
if (argc != 4) {
std::cerr << "Usage: " << argv[0] << " #iterations infile outfile" << std::endl;
return 1;
}
const int iterations = argv[1];
const std::string infile = argv[2];
const std::string outfile = argv[3];
for (v_it=mesh.vertices_begin(); v_it!=v_end; ++v_it)
if ( !mesh.is_boundary( *v_it ) )
mesh.set_point( *v_it, mesh.property(cogs,*v_it) );
}
// write mesh to stdout
if ( ! OpenMesh::IO::write_mesh(mesh, argv[3]) )
{
std::cerr << "Error: cannot write mesh to " << argv[3] << std::endl;
return 1;
}
// Read mesh file
if (!OpenMesh::IO::read_mesh(mesh, infile)) {
std::cerr << "Error: Cannot read mesh from " << infile << std::endl;
return 1;
}
return 0;
{
// Add a vertex property storing the computed centers of gravity
auto cog = OpenMesh::makeTemporaryProperty<OpenMesh::VertexHandle, MyMesh::Point>(mesh);
// Smooth the mesh several times
for (int i = 0; i < iterations; ++i) {
// Iterate over all vertices to compute centers of gravity
for (const auto& vh : mesh.vertices()) {
cog[vh] = {0,0,0};
int valence = 0;
// Iterate over all 1-ring vertices around vh
for (const auto& vvh : mesh.vv_range(vh)) {
cog[vh] += mesh.point(vvh);
++valence;
}
cog[vh] /= valence;
}
// Move all vertices to the previously computed positions
for (const auto& vh : mesh.vertices()) {
mesh.point(vh) = cog[vh];
}
}
} // The cog vertex property is removed from the mesh at the end of this scope
// Write mesh file
if (!OpenMesh::IO::read_mesh(mesh, outfile)) {
std::cerr << "Error: Cannot write mesh to " << outfile << std::endl;
return 1;
}
}
/** \page tutorial_03 Using (custom) properties
This examples shows:
- How to add and remove custom properties,
- How to add and remove custom properties
- How to get and set the value of a custom property
In the last example we computed the barycenter of each vertex'
......@@ -11,44 +11,69 @@ let %OpenMesh manage the data.
It would be even more helpful if we could attach such properties
dynamically to the mesh.
%OpenMesh provides dynamic properties, which can be attached to each
mesh entity (vertex, face, edge, halfedge, and the mesh itself). We
distinguish between custom and standard properties. A custom property
is any user-defined property and is accessed via the member function
\c property(..) via a handle and an entity handle
(e.g. VertexHandle). Whereas the standard properties are accessed via
special member functions, e.g. the vertex position is accessed with \c
point(..) and a vertex handle.
Custom properties can be conveniently created and attached to meshes with the following functions:
- makeTemporaryProperty() creates a property that is temporary to the current scope.
- getOrMakeProperty() is used for creating and accessing permanent named properties on a mesh.
- getProperty() is used for accessing an existing permanent named property on a mesh.
In this example we will store the \c cog-value (see previous example)
in an additional vertex property instead of keeping it in a separate
array. To do so we define first a so-called property handle with the desired
type (\c MyMesh::Point) and register the handle at the mesh:
All three functions take two template arguments:
- First, the type of the mesh element that the property is attached to (i.e. OpenMesh::VertexHandle, OpenMesh::HalfedgeHandle, OpenMesh::EdgeHandle, or OpenMesh::FaceHandle).
- Second, the type of the property value that is attached to each element (e.g., \p int, \p double, etc.).
All three functions return a handle object (of type OpenMesh::PropertyManager) that manages the lifetime of the property and provides read / write access to its values.
In this example, we will store the \c cog value (see previous example) in a vertex property instead of keeping it in a separate array.
To do so, we first add a (temporary) property of the desired element type (OpenMesh::VertexHandle) and value type (\c %MyMesh::Point) to the mesh:
\dontinclude 03-properties/smooth.cc
\skipline vertex property stores
\until mesh.add
\skipline makeTemporaryProperty
<br>The \c mesh allocates enough memory to hold as many elements of type
\c MyMesh::Point as number of vertices exist, and of course the mesh
synchronizes all insert and delete operations on the vertices with the
vertex properties.
Enough memory is allocated to hold as many values of \c %MyMesh::Point as there are vertices.
All insert and delete operations on the mesh are synchronized with the attached properties.
Once the wanted property is registered we can use the property to
calculate the barycenter of the neighborhood of each vertex \c v_it
Once the property is created, we can use it to compute the centers of the neighborhood of each vertex:
\dontinclude 03-properties/smooth.cc
\skipline vv_it=
\skipline mesh.vertices
\until cog[vv] /= valence
\until }
\until mesh.prop
<br>and finally set the new position for each vertex \c v_it
Finally, we set the new position for each vertex:
\dontinclude 03-properties/smooth.cc
\skipline mesh.set_point
\skipline mesh.vertices
\until mesh.point
\until }
---
Since we chose to use makeTemporaryProperty(), the created property is automatically removed from the mesh as soon as we leave the scope of the associated handle variable \c cog.
If, instead, a property is desired to survive its local scope, it should be created with using getOrMakeProperty(). In that case, the property must be given a name that can later be used to retrieve the property. For example:
\code
auto face_area = OpenMesh::makeTemporaryProperty<OpenMesh::FaceHandle, double>(mesh, 'face_area');
\endcode
At a later time, we can use the getProperty() function to obtain a handle to a property that was previously created by refering to its name:
\code
try {
auto face_area = OpenMesh::getProperty<OpenMesh::FaceHandle, double>(mesh, 'face_area');
// Use the face_area property.
}
catch (const std::runtime_error& e) {
// Property not found. Handle the error here.
}
\endcode
---
The functions makeTemporaryProperty(), getOrMakeProperty(), and getProperty() are the convenient high-level interface for creating and accessing mesh properties.
Beneath these convenience functions, there is also a low-level property interface where handle and property lifetime must be managed manually. This interface is accessed through a mesh's add_property(), remove_property(), and property() functions and several property handle classes (OpenMesh::VPropHandleT, OpenMesh::HPropHandleT, OpenMesh::EPropHandleT, OpenMesh::FPropHandleT, OpenMesh::MPropHandleT).
---
<br>Below is the complete source code:
Below is the complete source code:
\include 03-properties/smooth.cc
*/
\ No newline at end of file
*/
#ifndef HANDLETOPROPHANDLE_HH_
#define HANDLETOPROPHANDLE_HH_
#include <OpenMesh/Core/Mesh/Handles.hh>
#include <OpenMesh/Core/Utils/Property.hh>
namespace OpenMesh {
template<typename ElementT, typename T>
struct HandleToPropHandle {
};
template<typename T>
struct HandleToPropHandle<OpenMesh::VertexHandle, T> {
using type = OpenMesh::VPropHandleT<T>;
};
template<typename T>
struct HandleToPropHandle<OpenMesh::HalfedgeHandle, T> {
using type = OpenMesh::HPropHandleT<T>;
};
template<typename T>
struct HandleToPropHandle<OpenMesh::EdgeHandle, T> {
using type = OpenMesh::EPropHandleT<T>;
};
template<typename T>
struct HandleToPropHandle<OpenMesh::FaceHandle, T> {
using type = OpenMesh::FPropHandleT<T>;
};
template<typename T>
struct HandleToPropHandle<void, T> {
using type = OpenMesh::MPropHandleT<T>;
};
} // namespace OpenMesh
#endif // HANDLETOPROPHANDLE_HH_
\ No newline at end of file
......@@ -49,6 +49,8 @@
#ifndef PROPERTYMANAGER_HH_
#define PROPERTYMANAGER_HH_
#include <OpenMesh/Core/System/config.h>
#include <OpenMesh/Core/Utils/HandleToPropHandle.hh>
#include <sstream>
#include <stdexcept>
#include <string>
......@@ -60,35 +62,23 @@ namespace OpenMesh {
* It also defines convenience operators to access the encapsulated
* property's value.
*
* For C++11, it is recommended to use the factory functions
* makePropertyManagerFromNew, makePropertyManagerFromExisting,
* makePropertyManagerFromExistingOrNew to construct a PropertyManager, e.g.
* It is recommended to use the factory functions
* makeTemporaryProperty(), getProperty(), and getOrMakeProperty()
* to construct a PropertyManager, e.g.
*
* \code
* TriMesh mesh;
* auto visited = makePropertyManagerFromNew<VPropHandleT<bool>>(mesh, "visited.plugin-example.i8.informatik.rwth-aachen.de");
* {
* TriMesh mesh;
* auto visited = makeTemporaryProperty<VertexHandle, bool>(mesh);
*
* for (auto vh : mesh.vertices()) {
* if (!visited[vh]) {
* visitComponent(mesh, vh, visited);
* for (auto vh : mesh.vertices()) {
* if (!visited[vh]) {
* visitComponent(mesh, vh, visited);
* }
* }
* // The property is automatically removed at the end of the scope
* }
* \endcode
*
* For C++98, it is usually more convenient to use the constructor explicitly,
* i.e.
*
* \code
* TriMesh mesh;
* PropertyManager<VPropHandleT<bool>, TriMesh> visited(mesh, "visited.plugin-example.i8.informatik.rwth-aachen.de");
*
* for (TriMesh::VertexIter vh_it = mesh.begin(); ... ; ...) {
* if (!visited[*vh_it]) {
* visitComponent(mesh, *vh_it, visited);
* }
* }
* \endcode
*
*/
template<typename PROPTYPE, typename MeshT>
class PropertyManager {
......@@ -493,19 +483,127 @@ class PropertyManager {
std::string name_;
};
/** \relates PropertyManager
/** @relates PropertyManager
*
* Creates a new property whose lifetime is limited to the current scope.
*
* Used for temporary properties. Shadows any existing properties of
* matching name and type.
*
* Example:
* @code
* PolyMesh m;
* {
* auto is_quad = makeTemporaryProperty<FaceHandle, bool>(m);
* for (auto& fh : m.faces()) {
* is_quad[fh] = (m.valence(fh) == 4);
* }
* // The property is automatically removed from the mesh at the end of the scope.
* }
* @endcode
*
* @param mesh The mesh on which the property is created
* @param propname (optional) The name of the created property
* @tparam ElementT Element type of the created property, e.g. VertexHandle, HalfedgeHandle, etc.
* @tparam T Value type of the created property, e.g., \p double, \p int, etc.
* @tparam MeshT Type of the mesh. Can often be inferred from \p mesh
* @returns A PropertyManager handling the lifecycle of the property
*/
template<typename ElementT, typename T, typename MeshT>
PropertyManager<typename HandleToPropHandle<ElementT, T>::type, MeshT>
makeTemporaryProperty(MeshT &mesh, const char *propname = "") {
return PropertyManager<typename HandleToPropHandle<ElementT, T>::type, MeshT>(mesh, propname, false);
}
/** @relates PropertyManager
*
* Obtains a handle to a named property.
*
* Example:
* @code
* PolyMesh m;
* {
* try {
* auto is_quad = getProperty<FaceHandle, bool>(m, "is_quad");
* // Use is_quad here.
* }
* catch (const std::runtime_error& e) {
* // There is no is_quad face property on the mesh.
* }
* }
* @endcode
*
* @pre Property with the name \p propname of matching type exists.
* @throws std::runtime_error if no property with the name \p propname of
* matching type exists.
* @param mesh The mesh on which the property is created
* @param propname The name of the created property
* @tparam ElementT Element type of the created property, e.g. VertexHandle, HalfedgeHandle, etc.
* @tparam T Value type of the created property, e.g., \p double, \p int, etc.
* @tparam MeshT Type of the mesh. Can often be inferred from \p mesh
* @returns A PropertyManager wrapping the property
*/
template<typename ElementT, typename T, typename MeshT>
PropertyManager<typename HandleToPropHandle<ElementT, T>::type, MeshT>
getProperty(MeshT &mesh, const char *propname) {
return PropertyManager<typename HandleToPropHandle<ElementT, T>::type, MeshT>(mesh, propname, true);
}
/** @relates PropertyManager
*
* Obtains a handle to a named property if it exists or creates a new one otherwise.
*
* Used for creating or accessing permanent properties.
*
* Example:
* @code
* PolyMesh m;
* {
* auto is_quad = getOrMakeProperty<FaceHandle, bool>(m, "is_quad");
* for (auto& fh : m.faces()) {
* is_quad[fh] = (m.valence(fh) == 4);
* }
* // The property remains on the mesh after the end of the scope.
* }
* {
* // Retrieve the property from the previous scope.
* auto is_quad = getOrMakeProperty<FaceHandle, bool>(m, "is_quad");
* // Use is_quad here.
* }
* @endcode
*
* @param mesh The mesh on which the property is created
* @param propname The name of the created property
* @tparam ElementT Element type of the created property, e.g. VertexHandle, HalfedgeHandle, etc.
* @tparam T Value type of the created property, e.g., \p double, \p int, etc.
* @tparam MeshT Type of the mesh. Can often be inferred from \p mesh
* @returns A PropertyManager wrapping the property
*/
template<typename ElementT, typename T, typename MeshT>
PropertyManager<typename HandleToPropHandle<ElementT, T>::type, MeshT>
getOrMakeProperty(MeshT &mesh, const char *propname) {
return PropertyManager<typename HandleToPropHandle<ElementT, T>::type, MeshT>::createIfNotExists(mesh, propname);
}
/** @relates PropertyManager
* @deprecated Use makeTemporaryProperty() instead.
*
* Creates a new property whose lifecycle is managed by the returned
* PropertyManager.
*
* Intended for temporary properties. Shadows any existsing properties of
* Intended for temporary properties. Shadows any existing properties of
* matching name and type.
*/
template<typename PROPTYPE, typename MeshT>
PropertyManager<PROPTYPE, MeshT> makePropertyManagerFromNew(MeshT &mesh, const char *propname) {
OM_DEPRECATED("Use makeTemporaryProperty instead.")
PropertyManager<PROPTYPE, MeshT> makePropertyManagerFromNew(MeshT &mesh, const char *propname)
{
return PropertyManager<PROPTYPE, MeshT>(mesh, propname, false);
}
/** \relates PropertyManager
* @deprecated Use getProperty() instead.
*
* Creates a non-owning wrapper for an existing mesh property (no lifecycle
* management).
*
......@@ -516,22 +614,28 @@ PropertyManager<PROPTYPE, MeshT> makePropertyManagerFromNew(MeshT &mesh, const c
* matching type exists.
*/
template<typename PROPTYPE, typename MeshT>
PropertyManager<PROPTYPE, MeshT> makePropertyManagerFromExisting(MeshT &mesh, const char *propname) {
OM_DEPRECATED("Use getProperty instead.")
PropertyManager<PROPTYPE, MeshT> makePropertyManagerFromExisting(MeshT &mesh, const char *propname)
{
return PropertyManager<PROPTYPE, MeshT>(mesh, propname, true);
}
/** \relates PropertyManager
/** @relates PropertyManager
* @deprecated Use getOrMakeProperty() instead.
*
* Creates a non-owning wrapper for a mesh property (no lifecycle management).
* If the given property does not exist, it is created.
*
* Intended for creating or accessing persistent properties.
*/
template<typename PROPTYPE, typename MeshT>
PropertyManager<PROPTYPE, MeshT> makePropertyManagerFromExistingOrNew(MeshT &mesh, const char *propname) {
OM_DEPRECATED("Use getOrMakeProperty instead.")
PropertyManager<PROPTYPE, MeshT> makePropertyManagerFromExistingOrNew(MeshT &mesh, const char *propname)
{
return PropertyManager<PROPTYPE, MeshT>::createIfNotExists(mesh, propname);
}
/** \relates PropertyManager
/** @relates PropertyManager
* Like the two parameter version of makePropertyManagerFromExistingOrNew()
* except it initializes the property with the specified value over the
* specified range if it needs to be created. If the property already exists,
......@@ -552,7 +656,7 @@ PropertyManager<PROPTYPE, MeshT> makePropertyManagerFromExistingOrNew(
mesh, propname, begin, end, init_value);
}
/** \relates PropertyManager
/** @relates PropertyManager
* Like the two parameter version of makePropertyManagerFromExistingOrNew()
* except it initializes the property with the specified value over the
* specified range if it needs to be created. If the property already exists,
......
......@@ -93,7 +93,6 @@ TEST_F(OpenMeshPropertyManager, set_range_bool) {
ASSERT_TRUE(pm_f_bool[*f_it]);
}
#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || __cplusplus > 199711L || defined(__GXX_EXPERIMENTAL_CXX0X__)
/*
* Same thing again, this time with C++11 ranges.
*/
......@@ -129,15 +128,13 @@ TEST_F(OpenMeshPropertyManager, set_range_bool) {
f_it != f_end; ++f_it)
ASSERT_TRUE(pm_f_bool[*f_it]);
}
#endif
}
/*
* ====================================================================
* C++11 Specific Tests
* Factory Functions
* ====================================================================
*/
#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || __cplusplus > 199711L || defined(__GXX_EXPERIMENTAL_CXX0X__)
template<typename PropHandle, typename Mesh>
bool has_property(const Mesh& _mesh, const std::string& _name) {
......@@ -154,7 +151,7 @@ TEST_F(OpenMeshPropertyManager, cpp11_temp_property) {
ASSERT_FALSE(has_property<handle_type>(mesh_, prop_name));
{
auto vprop = OpenMesh::makePropertyManagerFromNew<handle_type>(mesh_, prop_name);
auto vprop = OpenMesh::makeTemporaryProperty<OpenMesh::VertexHandle, int>(mesh_, prop_name);
static_cast<void>(vprop); // Unused variable
ASSERT_TRUE(has_property<handle_type>(mesh_, prop_name));
}
......@@ -172,13 +169,13 @@ TEST_F(OpenMeshPropertyManager, cpp11_temp_property_shadowing) {
using handle_type = OpenMesh::VPropHandleT<int>;
const auto prop_name = "pm_v_test_property";
auto outer_prop = OpenMesh::makePropertyManagerFromNew<handle_type>(mesh_, prop_name);
auto outer_prop = OpenMesh::makeTemporaryProperty<OpenMesh::VertexHandle, int>(mesh_, prop_name);
outer_prop[vh] = 100;
ASSERT_EQ(100, outer_prop[vh]);
{
// inner_prop uses same type and name as outer_prop
auto inner_prop = OpenMesh::makePropertyManagerFromNew<handle_type>(mesh_, prop_name);
auto inner_prop = OpenMesh::makeTemporaryProperty<OpenMesh::VertexHandle, int>(mesh_, prop_name);
inner_prop[vh] = 200;
ASSERT_EQ(200, inner_prop[vh]);
// End of scope: inner_prop is removed from mesh_
......@@ -205,7 +202,7 @@ TEST_F(OpenMeshPropertyManager, cpp11_persistent_and_non_owning_properties) {
ASSERT_FALSE(has_property<handle_type>(mesh_, prop_name));
{
auto prop = OpenMesh::makePropertyManagerFromExistingOrNew<handle_type>(mesh_, prop_name);
auto prop = OpenMesh::getOrMakeProperty<OpenMesh::VertexHandle, int>(mesh_, prop_name);
prop[vh] = 100;
// End of scope, property persists
}
......@@ -214,7 +211,7 @@ TEST_F(OpenMeshPropertyManager, cpp11_persistent_and_non_owning_properties) {
{
// Since a property of the same name and type already exists, this refers to the existing property.
auto prop = OpenMesh::makePropertyManagerFromExistingOrNew<handle_type>(mesh_, prop_name);
auto prop = OpenMesh::getOrMakeProperty<OpenMesh::VertexHandle, int>(mesh_, prop_name);
ASSERT_EQ(100, prop[vh]);
prop[vh] = 200;
// End of scope, property persists
......@@ -224,7 +221,7 @@ TEST_F(OpenMeshPropertyManager, cpp11_persistent_and_non_owning_properties) {
{
// Acquire non-owning handle to the property, knowing it exists
auto prop = OpenMesh::makePropertyManagerFromExisting<handle_type>(mesh_, prop_name);
auto prop = OpenMesh::getProperty<OpenMesh::VertexHandle, int>(mesh_, prop_name);
ASSERT_EQ(200, prop[vh]);
}
......@@ -232,12 +229,13 @@ TEST_F(OpenMeshPropertyManager, cpp11_persistent_and_non_owning_properties) {
{
// Attempt to acquire non-owning handle for a non-existing property
ASSERT_THROW(OpenMesh::makePropertyManagerFromExisting<handle_type>(mesh_, "wrong_property_name"), std::runtime_error);
auto code_that_throws = [&](){
OpenMesh::getProperty<OpenMesh::VertexHandle, int>(mesh_, "wrong_prop_name");
};
ASSERT_THROW(code_that_throws(), std::runtime_error);
}
ASSERT_TRUE(has_property<handle_type>(mesh_, prop_name));
}
#endif
}
#include <gtest/gtest.h>
#include <OpenMesh/Core/Utils/PropertyManager.hh>
#include <Unittests/unittests_common.hh>
#include <string>
#include <map>
......@@ -448,36 +449,31 @@ TEST_F(OpenMeshTutorials, using_custom_properties) {
bool ok = OpenMesh::IO::read_mesh(mesh, "output.off");
EXPECT_TRUE(ok) << "Cannot read mesh from file 'output.off'";
// this vertex property stores the computed centers of gravity
OpenMesh::VPropHandleT<MyMesh::Point> cogs;
mesh.add_property(cogs);
const int iterations = 100;
// smoothing mesh N times
MyMesh::VertexIter v_it, v_end(mesh.vertices_end());
MyMesh::VertexVertexIter vv_it;
MyMesh::Point cog;
MyMesh::Scalar valence;
unsigned int i, N(100);
for (i=0; i < N; ++i)
{
for (v_it = mesh.vertices_begin(); v_it != v_end; ++v_it)
{
mesh.property(cogs,*v_it).vectorize(0.0f);
valence = 0.0;
for (vv_it = mesh.vv_iter( *v_it ); vv_it.is_valid(); ++vv_it)
{
mesh.property(cogs,*v_it) += mesh.point( *vv_it );
++valence;
// Add a vertex property storing the computed centers of gravity
auto cog = OpenMesh::makeTemporaryProperty<OpenMesh::VertexHandle, MyMesh::Point>(mesh);
// Smooth the mesh several times
for (int i = 0; i < iterations; ++i) {
// Iterate over all vertices to compute centers of gravity
for (const auto& vh : mesh.vertices()) {
cog[vh] = {0,0,0};
int valence = 0;
// Iterate over all 1-ring vertices around vh
for (const auto& vvh : mesh.vv_range(vh)) {
cog[vh] += mesh.point(vvh);
++valence;
}
cog[vh] /= valence;
}
// Move all vertices to the previously computed positions
for (const auto& vh : mesh.vertices()) {