Skip to content
Snippets Groups Projects
Commit 66ef0550 authored by Philip Trettner's avatar Philip Trettner
Browse files

more doc

parent d4a99aa8
Branches
No related tags found
No related merge requests found
......@@ -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.
......
......@@ -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
----------------
......
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);
......@@ -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).
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
......@@ -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!
......
......@@ -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;
......
......@@ -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;
}
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment