From 66ef05500a3d22686aa4ac771262f2dedea76d96 Mon Sep 17 00:00:00 2001 From: Philip Trettner <Philip.Trettner@rwth-aachen.de> Date: Wed, 20 Nov 2019 15:09:44 +0100 Subject: [PATCH] more doc --- docs/conf.py | 2 + docs/getting-started.rst | 11 +++ docs/mesh-topology.rst | 123 ++++++++++++++++++++++++++++++++-- docs/misc.rst | 6 ++ docs/reference.rst | 77 +++++++++++++++++++++ src/polymesh/Mesh.hh | 2 +- src/polymesh/cursors.hh | 2 - src/polymesh/low_level_api.hh | 4 -- 8 files changed, 214 insertions(+), 13 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index e3a7e3e..4488b18 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,6 +37,8 @@ extensions = [ 'breathe', ] +cpp_index_common_prefix = ['polymesh::'] + breathe_default_project = 'polymesh' # Add any paths that contain templates here, relative to this directory. diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 405324b..5d3b67b 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -31,6 +31,14 @@ Quickstart smoothed_pos[v] = v.adjacent_vertices().avg(pos); +Namespaces +---------- + +In its implementation, polymesh uses the ``polymesh::`` namespace. +However, long namespace are cumbersome and by default ``pm::`` is provided as an alias. +All examples will use the short version. + + CMake Integration ----------------- @@ -68,6 +76,9 @@ Working with meshes and attributes involves a lot of iteration. Often, aggregate statistics like averages, minimums, maximums, and mapped/filtered ranges are needed. Thus, polymesh provides a clean, composable, and powerful range API (see :doc:`mesh-topology` and :doc:`smart-ranges`). +Polymesh is performance-oriented but still concerned with usability and safety. +C++ Exceptions are not used but many assertions will trigger in Debug or RelWithDebInfo builds if the API is used wrongly. + Header Structure ---------------- diff --git a/docs/mesh-topology.rst b/docs/mesh-topology.rst index c447e98..3b85ae8 100644 --- a/docs/mesh-topology.rst +++ b/docs/mesh-topology.rst @@ -1,34 +1,145 @@ Mesh and Topology ================= -TODO - Mesh Class ------------- -TODO +The core data structure of polymesh is :class:`polymesh::Mesh`, a half-edge data structure for managing mesh topology. + +For an introduction to this type of data structure please refer to: + +* https://en.wikipedia.org/wiki/Doubly_connected_edge_list +* http://kaba.hilvi.org/homepage/blog/halfedge/halfedge.htm +* https://www.openmesh.org/media/Documentations/OpenMesh-Doc-Latest/a03930.html + +The Mesh only contains pure topology, no positions, no normals. +:doc:`attributes` are externally stored and behave like value types. +Because attributes and handles have a pointer to the mesh they belong to, a :class:`polymesh::Mesh` is non-copyable and non-movable. +Functions returning a mesh with new ownership should use ``std::unique_ptr<pm::Mesh>`` or ``std::shared_ptr<pm::Mesh>``. + +:func:`polymesh::Mesh::create` is a static helper function to create a ``unique_ptr<Mesh>`` though the typical way to just use ``pm::Mesh`` as a member or as a local variable. Memory Model ------------ -TODO +High-performance is one of the primary goals of polymesh and a cache-friendly contiguous data layout is often key to good performance. +Polymesh stores its topology in 6 arrays of ints: halfedge per face, outgoing halfedge per vertex, and vertex, face, next/prev halfedge per halfedge. +Primitives (vertices, edges, faces, halfedges) are strongly typed indices that point into these arrays (see next subsection). + +These arrays behave like a ``std::vector`` with size and capacity, using exponential reallocation for amortized O(1) to add a new primitive. +Deleting primitives does not invalidate any other handle or index nor does it move data. +The primitive is simply marked as "deleted" and is basically a hole in the array. +Iterating over primitives ignores deleted ones by default. +The function :func:`polymesh::Mesh::compactify()` can be used to make the mesh "compact" again, i.e. permuting all primitives such that no holes are left. +This invalidates handles. + +:doc:`attributes` mirror the memory layout of their respective primitive and are thus also affected by ``compactify()``. Handles and Indices ------------------- -TODO +Each primitive has a handle and an index version: + +* :class:`polymesh::vertex_index` and :class:`polymesh::vertex_handle` +* :class:`polymesh::face_index` and :class:`polymesh::face_handle` +* :class:`polymesh::edge_index` and :class:`polymesh::edge_handle` +* :class:`polymesh::halfedge_index` and :class:`polymesh::halfedge_handle` + +Roughly spoken, an index is a strongly typed integer representing the index of a primitive in a :class:`polymesh::Mesh`. +Handles are a combination of pointer to mesh and index such that topological operations like getting the face belonging to a halfedge can be done on the handle. + +Because handles contain a pointer to the mesh, they consume more memory than their corresponding index. +As a rule of thumb, if primitives are stored on the heap (e.g. in an ``std::vector`` or an ``std::set``) it makes sense to store the index. +For single primitives or local variables, handles are safer and more comfortable. + +Handles and indices can be explicitly cast to integer (``int(my_handle)``) to get the index. +Invalid indices and handles can be created (e.g. ``vertex_index::invalid``), which correspond to index ``-1``. + +Indices can be converted to handles. +For example, given ``Mesh m`` and ``vertex_index v`` (or as ``int i``), the following are handles (and equivalent): ``v.of(m)``, ``m[v]``, ``m.vertices()[i]``. + +Handles and indices can be "indexed" via subscript by attributes and functors (taking an index or handle, returning some type). +For example, given a ``vertex_attribute<tg::pos3> pos`` and a ``vertex_handle v``, the following are equivalent and return the position of the vertex: ``pos[v]`` and ``v[pos]``. +(This can be either read as "index the position attribute by v" or "return the position of v") + +Handles are "smart" in the sense that they can be directly used to iterate over mesh topology and query certain topological properties. +For example, given a ``face_handle f``, ``f.vertices()`` returns an iterable smart range containing all vertices of this face. +For all operations, see class references for :class:`polymesh::vertex_handle`, :class:`polymesh::face_handle`, :class:`polymesh::edge_handle`, and :class:`polymesh::halfedge_handle`. +For more information about the ranges, see :doc:`smart-ranges`. Primitive Collections --------------------- -TODO +Handles can be used to locally navigate the topology of a mesh. +Primitive collections are used for global topology navigation and for changing the topology. +Given ``Mesh m``, each primitive has its own collection: + +* :class:`polymesh::vertex_collection` via ``m.vertices()`` +* :class:`polymesh::face_collection` via ``m.faces()`` +* :class:`polymesh::edge_collection` via ``m.edges()`` +* :class:`polymesh::halfedge_collection` via ``m.halfedges()`` + +By default, iteration over these collections skips deleted primitives. +This is an additional check that can affect performance slightly. +If deleted primitives should be returned as well (or it is known that no deleted primitives exists, e.g. if :func:`polymesh::Mesh::is_compact` is true), then the ``all_<primitive>`` variants such as ``m.all_vertices()`` can be used. + +Topological operations such as adding faces, removing vertices, or splitting edges are all performed via their respective primitive collections. +E.g. ``m.faces().add(v0, v1, v2)`` adds a new triangle. + +Example: :: + + pm::Mesh m; + auto pos = pm::vertex_attribute<tg::pos3>(m); + + for (auto v : m.vertices()) + if (should_remove(v)) + m.vertices().remove(v); + + tg::rng rng; + auto f = m.faces().random(rng); + + auto centroid = f.vertices().avg(pos); + auto v = m.faces().split(f); + pos[v] = centroid; Low-Level API ------------- +Primitive collections and handles are a kind of "high-level API". +They perform many security checks, handle special cases, and sometimes have to do extra work to preserve some useful invariants (like the face-to-halfedge mapping always pointing to a boundary if the face lies on a boundary to have a super fast :func:`polymesh::face_handle::is_boundary` test). + +Sometimes, algorithms want to manipulate the half-edge data structure directly and bypass the high-level API. +This can be done by using the helper function :func:`polymesh::low_level_api` which returns a simple wrapper object that can be used for manipulating the internals of a :class:`polymesh::Mesh`. + TODO + + +Copying a Mesh +-------------- + +Handles and attributes (which are external) refer to a mesh by reference, which makes it nontrivial to copy a mesh with all attributes. +For security reasons, handles to one mesh cannot be used to index into a different mesh unless the handle is cast into an index beforehand. + +To make copying a mesh less tedious a few helpers exist: + +* :func:`polymesh::Mesh::copy_from` clears a mesh and copies over the topology of another mesh +* :func:`polymesh::Mesh::copy` creates a new ``unique_ptr<Mesh>`` and copies over the topology +* ``attribute::copy_from(attribute const&)`` copies attribute data from a different mesh +* ``attribute::copy_to(Mesh const&)`` copies attribute data to a different mesh +* :func:`polymesh::copy` creates a mesh and an arbitrary number of attributes and creates copies of them all (returned as tuple) + +Example: :: + + // must be included explicitly because + // it needs the relatively expensive header <tuple> + #include <polymesh/copy.hh> + + pm::Mesh m; + auto pos = pm::vertex_attribute<tg::pos3>(m); + + auto [m2, pos2] = pm::copy(m, pos); diff --git a/docs/misc.rst b/docs/misc.rst index 5edd509..41255bb 100644 --- a/docs/misc.rst +++ b/docs/misc.rst @@ -5,3 +5,9 @@ Assertions ---------- +Simple Graphs +------------- + +While polymesh is primarily a mesh data structure it can also be used to represent simple graphs, i.e. undirected graphs without self loops and without multi-edges. +Half-edge data structures cannot represent most non-manifold geometry but that limitations only applies if faces are added. +A "wireframe mesh" (mesh without faces) can be of arbitrary topology as long as edges are unique (no multi-edge) and start and end vertex are different (no self loops). diff --git a/docs/reference.rst b/docs/reference.rst index 257541b..abe7e26 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -1,9 +1,69 @@ Class Reference =============== +Mesh +---- + .. doxygenclass:: polymesh::Mesh :members: +Handles and Indices +------------------- + +.. doxygenstruct:: polymesh::primitive_index + :members: + +.. doxygenstruct:: polymesh::face_index + :members: + +.. doxygenstruct:: polymesh::vertex_index + :members: + +.. doxygenstruct:: polymesh::edge_index + :members: + +.. doxygenstruct:: polymesh::halfedge_index + :members: + +.. doxygenstruct:: polymesh::primitive_handle + :members: + +.. doxygenstruct:: polymesh::face_handle + :members: + +.. doxygenstruct:: polymesh::vertex_handle + :members: + +.. doxygenstruct:: polymesh::edge_handle + :members: + +.. doxygenstruct:: polymesh::halfedge_handle + :members: + +Ranges and Collections +---------------------- + +.. doxygenstruct:: polymesh::smart_range + :members: + +.. doxygenstruct:: polymesh::smart_collection + :members: + +.. doxygenstruct:: polymesh::face_collection + :members: + +.. doxygenstruct:: polymesh::vertex_collection + :members: + +.. doxygenstruct:: polymesh::edge_collection + :members: + +.. doxygenstruct:: polymesh::halfedge_collection + :members: + +Attributes +---------- + .. doxygenstruct:: polymesh::vertex_attribute :members: @@ -15,3 +75,20 @@ Class Reference .. doxygenstruct:: polymesh::halfedge_attribute :members: + +.. doxygenstruct:: polymesh::primitive_attribute + :members: + +Low-Level API +------------- + +.. doxygenstruct:: polymesh::low_level_api_base + :members: + +.. doxygenstruct:: polymesh::low_level_api_mutable + :members: + +Helper +------ + +.. doxygenfunction:: polymesh::copy diff --git a/src/polymesh/Mesh.hh b/src/polymesh/Mesh.hh index 57bb10f..a15e617 100644 --- a/src/polymesh/Mesh.hh +++ b/src/polymesh/Mesh.hh @@ -117,7 +117,7 @@ public: Mesh& operator=(Mesh const&) = delete; Mesh& operator=(Mesh&&) = delete; - /// Creates a new mesh and returns a shared_ptr to it + /// Creates a new mesh and returns a unique_ptr to it static unique_ptr<Mesh> create() { return make_unique<Mesh>(); } /// Clears this mesh and copies mesh topology, NOT attributes! diff --git a/src/polymesh/cursors.hh b/src/polymesh/cursors.hh index c8b8a7e..17a8ee1 100644 --- a/src/polymesh/cursors.hh +++ b/src/polymesh/cursors.hh @@ -156,8 +156,6 @@ struct vertex_handle : primitive_handle<vertex_tag> halfedge_handle any_incoming_halfedge() const; ///< invalid if isolated edge_handle any_edge() const; ///< invalid if isolated - // TODO: make all_faces and faces = all_faces.filter(is_valid) - vertex_halfedge_in_ring incoming_halfedges() const; vertex_halfedge_out_ring outgoing_halfedges() const; vertex_edge_ring edges() const; diff --git a/src/polymesh/low_level_api.hh b/src/polymesh/low_level_api.hh index 68dc588..aebcc64 100644 --- a/src/polymesh/low_level_api.hh +++ b/src/polymesh/low_level_api.hh @@ -189,11 +189,7 @@ struct low_level_attribute_api { MeshT mesh; auto attr = mesh.vertices().make_attribute_with_default(0.f); - auto attr_check = mesh.vertices().make_attribute_with_default(std::array<float, 32>()); - auto offset = int(size_t(&(attr.mData)) - size_t(&attr)); - POLYMESH_ASSERT(offset == int(size_t(&(attr_check.mData)) - size_t(&attr_check))); - return offset; } }; -- GitLab