olson-sean-k / plexus

Polygonal mesh processing.
https://plexus.rs
MIT License
162 stars 14 forks source link

Add more examples #14

Open virtualritz opened 6 years ago

virtualritz commented 6 years ago

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.

olson-sean-k commented 6 years ago

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.

virtualritz commented 6 years ago

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.

olson-sean-k commented 6 years ago

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.

virtualritz commented 5 years ago

Bump. :)

P.S.: Happy New Year.

anderejd commented 5 years ago

@virtualritz While waiting for plexus to mature I would recommend taking a look at https://github.com/huxingyi/meshlite

Happy New Year ⭐️

olson-sean-k commented 5 years ago

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. :-)

virtualritz commented 4 years ago

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.

olson-sean-k commented 4 years ago

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 structs, enums, 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!

virtualritz commented 4 years ago

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.

olson-sean-k commented 4 years ago

...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.

olson-sean-k commented 4 years ago

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 MeshBuffers) that describe heterogeneous polygons with arbitrary arity.

virtualritz commented 4 years ago

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()?

olson-sean-k commented 4 years ago

...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:

virtualritz commented 3 years ago

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.

virtualritz commented 3 years ago

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.

olson-sean-k commented 3 years ago

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.

virtualritz commented 3 years ago

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.

olson-sean-k commented 3 years ago

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.

virtualritz commented 3 years ago

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:

  1. Allow bad/non-manifold topologies to be represented by Plexus.
  2. Reject creating such geometries.
  3. Allow fixing them; ideally during creation.

Any user-selectable combination of those three is also nice, ofc.

ibash commented 2 years ago

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?

olson-sean-k commented 2 years ago

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.

virtualritz commented 2 years ago

@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.