jcornaz / heron

[DISCONTINUED] An ergonomic physics API for bevy games
MIT License
292 stars 44 forks source link

Access to shape data #65

Open faassen opened 3 years ago

faassen commented 3 years ago

We have a use case where we want to access shape data in the renderer code. For instance, ConvexHull creates a set of points that defines the convex hull depending on the points you give it. It should be possible to access this data through a Heron API. This issue is to remind us to think about this.

In Rapier, once you have the shape, you can call this:

shape.as_convex_polygon().unwrap().points()

Where as_convex_polygon is a typecast and then points is the method that returns the points to draw.

Do we want to expose some kind of typecast API that can fail like this or is there another pattern that would work better? I know absolutely nothing about the reflection system of Bevy, is that relevant?

We need a Heron version of a convex polygon, that exposes Bevy vectors instead of Rapier points.

See also #43 as a fallback on how to get to the Rapier world. This could be a quick way to implement it without any new Heron APIs for the debug renderer, but in the longer term it would be better to expose this information in some nice Heron API.

faassen commented 3 years ago

So looking at debug dim2.rs as it is now as an example of something that needs access to Rapier shape data, it works as follows:

This logic is required for ConvexHull but not for the other body types so far as the complete shape data is available in the original heron_core::Body.

What do we expect for Heightfield (#62), Cylinder (#63) and Cone (#64)? It looks like those can be entirely defined upon creation, though I'm not entirely clear about what Heightfield does yet.

So we could treat ConvexHull as an exception.

On the other hand, both ConvexHull, Cuboid and potentially in the future also Triangle all can be treated as polygons with a vector of points. Would it be possible to generalize this as a Points component? Perhaps:

struct Points {
  points: Option<Vec<Vec3>>
}

which defines Points only if such exist, and this is updated upon startup automatically after the physics engine is done initializing. If we added an additional optional border_radius field, we could support round cuboids, round triangles, and round_convex_hull as well. This would allow, at least in 2d, for more general debug code which can draw all of these as polygons.

jcornaz commented 3 years ago

Yeah, what I did for the ConvexHull (#60) was more a "work-around" than the actual solution to the present issue.

What do we expect for Heightfield (#62), Cylinder (#63) and Cone (#64)? It looks like those can be entirely defined upon creation, though I'm not entirely clear about what Heightfield does yet.

Yes, I think we'll have all information we needs in the definition of the Body variant.

So we could treat ConvexHull as an exception.

Yes. Alghough I'm never fan of having exceptions, I don't think there is much way around it. If we create a generalized approach for polygons, then the sphere, capsule, cylinder and cone would become the exceptions.

On the other hand, both ConvexHull, Cuboid and potentially in the future also Triangle all can be treated as polygons with a vector of points. Would it be possible to generalize this as a Points component?

Although technially possible, and maybe even "nice" for the debug renderer, I'm not eager to add public component for this. The thing is the debug render is only a debug tool. Ideally, I would like to extend the API (public components/resources, etc.) only to support the physics manipulation. Otherwise, it may make stuff confusing. For instance what should happen if the user mutate that Points component?

Anyway, it would be possible to have a general approach in the debug render without such component:

fn base_builder(body: &Body, shape: &dyn Shape) -> GeometryBuilder {
    let mut builder = GeometryBuilder::new();

    // Try to display a polygon if possible (ignoring the content of Body)
    if let Some(polygon) = shape.as_convex_polygon() {
        builder.add(&shapes::Polygon {
            points: polygon.points().into_bevy(),
            closed: true,
        });
        return builder;
    }

    // It wasn't possible to draw a polygon. Inspect the `Body` component:
    match body {
        Body::Sphere { radius } => ...
        Body::Capsule { half_segment, radius } => ...
        _ -> eprintln!("Some collision shapes couldn't be rendered")
    };

    builder
}
faassen commented 3 years ago

To be clear: I'm not concerned with the debug renderer. I have a non-debug renderer that also uses this information, so I'd like there to be a public API for this. I thought Heron is all about exposing the Rapier APIs in a more friendly way, integrated with Bevy?

In your example shape still comes from Rapier (in fact, Parry!) and you need to get it in the complex way I described, and then use into_bevy(). That's not a great easy to use API to get this information.

Note that your example is not generic in the way I describe with Points, as I was proposing to let other things such as Cuboid have points too. as_convex_polygon as far as I understand it, wouldn't work for Cuboid or Triangle. But having this be generic is a secondary concern; the primary one is having an API at all that doesn't require a lot of knowledge about Rapier.

If there is no way to get this information, I wonder whether we should support ConvexHull at all in Heron. ConvexPolyline would be better as this can be reliably drawn without having to introspect Rapier. But it wouldn't work for 3d, only 2d.

Concerning mutating a points component - I guess read-only components aren't a thing then in Bevy? It strikes me those would be useful if you're integrating Bevy with external systems like physics engines in general.

Another way would be to expose a resource to get the points data for a body or entity. Is there is a way to get from a heron Body to a Rapier/Parry Shape? Or would you have to go through entity?

jcornaz commented 3 years ago

To be clear: I'm not concerned with the debug renderer. I have a non-debug renderer that also uses this information, so I'd like there to be a public API for this.

Ah, ok. I misunderstood then. Sorry.

That of course invalidate my previous comment.

I think I would call the component ShapeData, or something like that.

And if we are to expose the shape data, I would try to do it for every shape, not only for the polygons. Maybe we need some SVG-like API:

struct ShapeData {
  path: Vec<ShapeSegment>
}

enum ShapeSegment {
  LineTo(Vec3),
  ArcTo(...)
}

To answer myself about mutability of the component we have to:

jcornaz commented 3 years ago

If we provided a way to create a Body from a Mesh (in 3d) or a bevy_prototype_lyon's polygon (in 2d), would that fullfill your use-case @faassen? Or would you still need to access the shape data?

faassen commented 3 years ago

If there were a way to create a body from a bevy_prototype_lyon polygon (which then would be a convex_polygon instead of a convex hull?) then I think that would fulfill my use case and in fact reduce some duplication (as I stole the regular polygon code from bevy_prototype_lyon in the first place to create a regular Rapier polygon).

The SVG-like API to draw any shape sounds attractive in general but wouldn't be required for my use case.

jcornaz commented 3 years ago

Maybe we could introduce a Body::FromConvexPolygon and Body::FromConvexMesh variants to Body. Probably behind appropriate feature flags.

And it would create a collider that is the convex-hull of the polygon/mesh component found on the same entity.

I'm not yet sure if that is a good or a bad idea. I will think more about it.