plexus
plexus copied to clipboard
Add more examples
I'm a Rust n00b but I have 25+ years of experience working with geometry in C/C++ (e.g. using libs like OpenMesh). The reference docs for Plexus are extremely thin even for someone like me. I.e. I have to do a lot of trial and error when using this crate.
It would really be helpful if there were some more examples. Maybe some code that shows how to convert an indexed mesh, loaded e.g. from an OBJ, into a graph and then perform some basic topology modifying operation on it – like collapsing an edge or extruding a face.
Thanks for taking a look and for the feedback! Documentation is certainly lacking. This is largely because Plexus is still a bit of an experiment and is quite far from being ready for production. Many fundamental features are missing and there is still too much churn to make user guides just yet. Documentation will be a major goal before reaching a stable 0.1.0
release though.
Bump! :) Making examples is also eating your own dogfood. From my experience it can help with designing APIs – even if you have to change the example code later.
Sorry for the radio silence on this. I'm debating using GitHub's wiki feature to write a few minimal examples. In the meantime, have you taken a look at the crate documentation? There are various short examples in the comments. They're very small, but Plexus isn't capable of much just yet and they cover some of the basics of generators, buffers, and meshes.
Bump. :)
P.S.: Happy New Year.
@virtualritz While waiting for plexus to mature I would recommend taking a look at https://github.com/huxingyi/meshlite
Happy New Year ⭐️
I'm currently working on a website with a user guide in a separate repository using mkdocs. The user guide is still sparse, but I'm planning on offloading visualizations and more in-depth explanations from the API documentation (i.e., rustdoc
) and into this user guide. It offers some minimal examples. See the graph section for the most complete example so far.
I'm hoping this kind of user guide will be helpful. Once I complete some more features, I hope to add more comprehensive examples to the repository, but there's still a lot to do. :-)
Could you add an example on how to get index buffers for a mesh that is made of n-gons (with n being non-uniform over the mesh)?
I would like to use plexus with my nsi crate to render generative graphs as subdivision surfaces with creases and corners.
I need one buffer with face vertex numbers and the index and vertex buffers. The docs mention the latter two but I do not grok how to get the first buffer (number of vertices of each face) from a MeshBuffer
.
The structure of a MeshBuffer
's index buffer is determined by its R
type parameter, which must implement the Grouping
trait. At the moment, the types that implement Grouping
are the Flat
meta-grouping for unstructured index buffers with a fixed arity (all polygons have the same number of sides and the buffer is a sequence of indices) and types that implement Topological
with unsigned integer vertex data (the buffer contains struct
s, enum
s, etc. of index data). The documentation for Grouping
may be helpful here.
To get a MeshBuffer
with an index buffer with non-uniform arity, Polygon
is currently the only choice. This is limiting, because Polygon
only supports triangles and quadrilaterals. I've been considering a more general type for this with different trade-offs that allows for indexing polygons with arbitrary arity, but that isn't possible yet.
Here's an example of a MeshBuffer
with faces (and so an index buffer) composed of triangles and quadrilaterals using Polygon<usize>
:
use decorum::R64;
use nalgebra::Point2;
use plexus::buffer::MeshBuffer;
use plexus::prelude::*;
use plexus::primitive::{Tetragon, Trigon};
pub type E2 = Point2<R64>;
let buffer = MeshBuffer::<Polygon<usize>, E2>::from_raw_buffers(
// Index data is composed of `Polygon`s (formed from `Trigon`s and `Tetragon`s).
vec![
Trigon::new(...).into(),
Trigon::new(...).into(),
Tetragon::new(...).into(),
Tetragon::new(...).into(),
],
// Vertex data is composed of `E2`s (positional data).
vec![...],
)
.unwrap();
for polygon in buffer.as_index_slice() {
match *polygon {
Polygon::N3(trigon) => { // Triangle of indices.
let [a, b, c] = trigon.into_array();
...
}
Polygon::N4(tetragon) => { // Quadrilateral of indices.
let [a, b, c, d] = tetragon.into_array();
...
}
}
}
Polygon
may not be able to support your use case if you need anything other than triangles and quadrilaterals, I'm afraid. 😞 I hope I understood the question; please let me know if I'm off track!
That's totally cool for now. I guess this limitation will be lifted at some stage? Plexus is an ideal base to store meshes for manipulation in e.g.a 3D modeling software. But for that use case 2-manifolds composed of polygons with arbitrary arity >2 (each) are mandatory.
...2-manifolds composed of polygons with arbitrary arity >2 (each) are mandatory.
Agreed. MeshGraph
already supports this, but its implementation is still immature and is likely a bit of a minefield. I do plan to provide more flexible index buffers (probably based on SmallVec
). Once that's done, MeshBuffer
should also support polygons with non-uniform and arbitrary arity.
~~These changes~~ [landed on master
; see 1b8d573 and subsequent commits] introduce a distinction between bounded and unbounded polygons along with support for index buffers (and therefore MeshBuffer
s) that describe heterogeneous polygons with arbitrary arity.
Ohh ... amazing. So is the for
..match
above still needed to build the vertices/polygon buffer or is there a shortcut for this now? collect_topology_into_flat_buffer()
?
...or is there a shortcut for this now?
collect_topology_into_flat_buffer()
?
There's no special function; collect
works just the same. The important bit is the type MeshBuffer<UnboundedPolygon<_>, _>
. The above example would look more like this:
use decorum::R64;
use nalgebra::Point2;
use plexus::buffer::MeshBuffer;
use plexus::prelude::*;
use plexus::primitive::UnboundedPolygon;
pub type E2 = Point2<R64>;
let buffer = MeshBuffer::<UnboundedPolygon<usize>, E2>::from_raw_buffers(
// Index data is composed of arbitrary polygons.
vec![
UnboundedPolygon::try_from_slice(...).unwrap(),
UnboundedPolygon::trigon(...),
UnboundedPolygon::tetragon(...),
...,
],
// Vertex data is composed of `E2`s (positional data).
vec![...],
)
.unwrap();
for polygon in buffer.as_index_slice() {
for index in polygon.as_ref() {
...
}
}
It is not possible to match
on an UnboundedPolygon
. Instead, use the Index
, AsRef
, and DynamicArity
trait implementations to determine a polygon's arity and access its data.
Here's an example of collecting into a buffer:
type E3 = Point3<R64>;
type IndexPolygon = UnboundedPolygon<usize>;
let buffer: MeshBuffer<IndexPolygon, E3> = Cube::new()
.polygons::<Position<E3>>()
.collect();
Importantly, UnboundedPolygon
works with indexers and collection just like other types that support index buffers. Unlike the old Polygon
type (now named BoundedPolygon
), UnboundedPolygon
can have any arity of three or greater. The most general way to construct an UnboundedPolygon
is UnboundedPolygon::try_from_slice
.
Of course, this is work in progress, so some of this could change a bit. :smile:
I tried this to pull a Vec<MeshGraph>
from an OBJ file but it seems I am still missing something:
use decorum::R64;
use itertools::Itertools;
use nalgebra::Point3;
use plexus::prelude::*;
use plexus::{buffer::MeshBuffer, graph::MeshGraph, primitive::UnboundedPolygon};
use std::convert::TryFrom;
use tobj::load_obj;
type E3 = Point3<R64>;
let object = tobj::load_obj(
"test.obj", false, // Do not triangulate on import
);
let (models, _) = object.unwrap();
let models: Vec<MeshGraph> = models
.into_iter()
.filter_map(|model| {
let mesh = model.mesh;
let mut end = 0usize;
let buffer = MeshBuffer::<UnboundedPolygon<u32>, E3>::from_raw_buffers(
mesh.num_face_indices
.into_iter()
.filter_map(|face_arity| {
let start = end;
end += face_arity as usize;
UnboundedPolygon::try_from_slice(&mesh.indices[start..end])
})
.collect::<Vec<_>>(),
mesh.positions
.into_iter()
.tuples::<(_, _, _)>()
.map(|p| {
E3::new(
R64::try_from(p.0 as f64).unwrap(),
R64::try_from(p.1 as f64).unwrap(),
R64::try_from(p.2 as f64).unwrap(),
)
})
.collect::<Vec<_>>(),
)
.unwrap();
MeshGraph::try_from(&buffer).ok()
})
.collect();
The MeshGraph::try_from(&buffer).ok()
line gives me some error that brings back fond memories of fighting templates in C++. 😜
I'm building against current master
.
Ideally I would like to skip the intermediate step of using a MeshBuffer
ofc. But when I tried that I got even more crazy errors.
Interesting, thanks for sharing!
At some point I'll try building the code you've shared so I can see what errors you've encountered. Plexus leans heavily toward abstraction and one big downside is that type errors can get gnarly pretty fast. 😞
I've created a (temporary) branch and gist with a working example that loads the Newell teapot from an OBJ file. To accomplish this, OBJ meshes are converted into raw buffers. Because tobj
exposes vertex attributes like positions as flattened scalars, the example composes these into structured data and must re-index. From there, both MeshGraph
and MeshBuffer
can be trivially constructed. The example is a bit bloated, because it includes rendering.
As noted in that code, I encountered an issue while trying this. For that specific model, the resulting MeshGraph
appears to have a corrupted vertex! I didn't encounter this with some other models and I'm not yet sure why this happens.
Was that part of the example fixed in plexus
, in the meantime?
// TODO: This particular model seems to corrupt the resulting graph! The
// arc circulator for a particular vertex never terminates. For
// example, computing the vertex normal hangs. This does not seem
// to happen with other similar models.
It sounded to me like while it may work mostly it wouldn't work sometimes. For my application this is a deal breaker.
Was that part of the example fixed in
plexus
, in the meantime?
Not yet, no. This is one of the next things I'd like to work on once I find the time (probably after shenanigans with stable constant generics).
It sounded to me like while it may work mostly it wouldn't work sometimes. For my application this is a deal breaker.
I only tried this with three OBJ files, so it isn't clear how often this breaks (or why, for that matter). I think this boils down to two broad possibilities: (1) the model data is corrupt or incompatible and this is not detected or (2) there is a bug in MeshGraph
that, in this case, constructs a bad vertex in the graph despite receiving perfectly good data. I would consider either of these bugs in Plexus, though the latter is more serious.
I would consider either of these bugs in Plexus, [...]
Cool.
I had to implement automatic detection of subdivision surface creases/corners from (discontinuous) per face per vertex normals on indexed meshes yesterday. And that wasn't nearly as much fun as it could have been if I could use Plexus.
OpenMesh allows fixing non-manifold topology on-the-fly as a graph is built. This is a nice feature. Wings3D also does this, when importing such geometry.
I see three possibilities:
- Allow bad/non-manifold topologies to be represented by Plexus.
- Reject creating such geometries.
- Allow fixing them; ideally during creation.
Any user-selectable combination of those three is also nice, ofc.
FWIW as both a rust noob and geometry noob I couldn't figure out how to use plexus. I looked through the user guide, read through the code for plexus and theon.
My current understanding is that to fully benefit from plexus you'd need to implement custom types for it right? It's not a plug/play library?
FWIW as both a rust noob and geometry noob I couldn't figure out how to use plexus.
Plexus leans pretty heavily toward abstraction and leverages Rust's type system to do so. I would expect that to be a non-trivial barrier to folks that aren't too familiar with Rust (and there's stuff that I think those familiar with Rust would still find a bit gnarly, to be honest). This isn't always great, but my aim has been to provide a sort of middleware that is flexible and composable and, if possible, simplify things later. Sorry that it's been difficult to grok. That all being said, if you have a specific question, then I'm happy to offer my two cents!
My current understanding is that to fully benefit from plexus you'd need to implement custom types for it right? It's not a plug/play library?
Custom types and trait implementations aren't required. I've taken a bit of a hiatus on Plexus and Theon for a while, but if you're willing to use older versions of the supported crates, then all that is needed is to enable the appropriate Cargo features and everythign should Just Work™. For example, if you'd like to use nalgebra
to represent geometry, then enable the geometry-nalgebra
feature and use the types re-exported in plexus::integration::nalgebra
(and similary for ultraviolet
, glam
, etc.).
On the other hand, if you'd like to use unsupported types or roll your own, then you'll need to either implement the geometric traits for them in Theon or use functions that are agnostic to geometry and do the computations via ad-hoc user code (generally via functions or closures). For example, rather than using extrude_with_translation
, which requires types that implement geometric traits, use extrude_with
and provide a function that computes the necessary vertex geometry.
Just for some additional context: Theon is an attempt to provide a set of Euclidean geometry traits that can be implemented throughout the Rust ecosystem. This is sort of the opposite approach from Mint, which provides datagrams and conversions for interopability. That's pretty hard to get right, so I decided to initially provide implementations for third-party crates myself gated by Cargo features so I could develop it freely (as opposed to asking upstream crates to provide an integration). Unfortunately, that's difficult to maintain, so those implementations tend to fall behind. I'm working on splitting Theon into a trait-only crate and a more agile layer on top with geometric structures and algorithms. If and when that lands, it will likely include implementations for much more recent versions of those crates.
@olson-sean-k are you aware of any third party projects that use plexus and/or theon in the wild whose source is available?
Looking at such code is usually my first stop to grok stuff when I use a new crate whose documentation eludes me in some way.