godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Expose and improve physics test/solver methods #9861

Open bud11 opened 3 months ago

bud11 commented 3 months ago

Describe the project you are working on

A 3D action game with moderate hitbox usage and some custom physics stuff

Describe the problem or limitation you are having in your project

Generally, its really obtuse and complex to do something like check if/where two collisionobject3ds overlap. I use that to check if and where characters and hitboxes are touching for example. I was also going to use it for part of a custom verlet integration implementations constraint stage.

The only way Ive found to do this is like so, and even this only considers the first shape on each object (Ive tried to extend it to cover all involved shapes but I just couldnt figure out how). This surely isnt optimal - I only want the point of contact between two shapes, I dont want to have to solve for all remotely involved shapes, twice.

cs
//cleared at the end of every phys frame
public static Dictionary<CollisionObject3D, Rid[]> IntersectCache = new();

public static Vector3? GetShapesIntersectionPoint(CollisionObject3D body1, CollisionObject3D body2, float Margin = 0f)
{

    //I have no idea why this is nessecary, it really shouldnt be
    if (body1.CollisionLayer == 0b0000 || body2.CollisionLayer == 0b0000) return null;

    var Query = new PhysicsShapeQueryParameters3D()
    {
        Shape = body1.ShapeOwnerGetShape(body1.ShapeFindOwner(0), 0),
        Transform = body1.GlobalTransform * body1.ShapeOwnerGetTransform(body1.ShapeFindOwner(0)),

        CollideWithBodies = true,
        CollideWithAreas = true,

        Margin = Margin
    };

    if (!IntersectCache.ContainsKey(body1)) IntersectCache[body1] = SpaceState.IntersectShape(Query, 14).Select(x => ((CollisionObject3D)x["collider"]).GetRid()).ToArray();
    Rid[] Cols = IntersectCache[body1];

    Rid body2rid = body2.GetRid();

    if (Cols.Contains(body2rid))
    {
        Query.Exclude = new Godot.Collections.Array<Rid>(Cols.Where(x => x != body2rid));

        Query.CollideWithBodies = (!(body1 is Area3D)) || (!(body2 is Area3D));
        Query.CollideWithAreas = (body1 is Area3D) || (body2 is Area3D);

        Query.CollisionMask = body1.CollisionLayer | body2.CollisionLayer;

        var CollisionPoints = SpaceState.CollideShape(Query, 1).ToArray();

        if (CollisionPoints.Any()) return CollisionPoints[0];
    }

    return null;
}

It should also be noted that almost none of this was clearly documented or mentioned anywhere online and it took a lot of headache to ever figure this out.

The closest two things I found were much more vague and much more specific to both characterbody3d and area3d respectively (those being the test move methods and the area3d overlap signal with its shape arguments).

Describe the feature / enhancement and how it helps to overcome the problem or limitation

It'd be nice if there were some generic, intuitive methods on PhysicsServer to run overlap/collision tests with full detail. It'd also be nice if the documentation and/or workings for things like shape ids were cleaned up massively, but really this isnt a low level use case or anything.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Im not 100% clear on how the source works even after looking but surely physics overlap tests like this could be exposed without too much trouble? I can imagine something like a PhysicsServer.CollisionObjectSolve() that operates vaguely similarly to CollideShape in the sense that you give it a query of parameters (for things like margins, transforms, masks/layers), and it gives you a detailed struct or something in return with info like point of contact, normal.

The key thing here is that this works with specifically two shapes, so you can iterate with multiple shapes or do whatever you want however you want in cases like this.

If this enhancement will not be used often, can it be worked around with a few lines of script?

Its clearly possible to some degree but its not very performant or sensical

Is there a reason why this should be core and not an add-on in the asset library?

Simple intuitive feature, would be more performant if implemented natively, etc

bud11 commented 3 months ago

Should also mention it might be nice if you could raycast onto specifically one shape/object or a whitelist of objects. That'd really help with the verlet integration thing I mentioned.

I know some of this sounds like it could be solved with collision layers/masks, but mask/layer changes dont update immediately making it impossible to shuffle layers around momentarily for whitelists, and using a billion unique layers isnt optimal or scalable enough for a real project

AThousandShips commented 3 months ago

Should also mention it might be nice if you could raycast onto specifically one shape/object or a whitelist of objects.

This wouldn't be trivial as that's not how the engine works internally, it uses spatial indexing that speeds up the processing, assuming you mean just doing ray casting with a specific subset (that could be done with worse performance just checking the shape(s) against a ray directly)

Otherwise you'd just do a ray cast and check the result against your desired objects so I assume that's not what was intended

bud11 commented 3 months ago

Should also mention it might be nice if you could raycast onto specifically one shape/object or a whitelist of objects.

This wouldn't be trivial as that's not how the engine works internally, it uses spatial indexing that speeds up the processing, assuming you mean just doing ray casting with a specific subset (that could be done with worse performance just checking the shape(s) against a ray directly)

Otherwise you'd just do a ray cast and check the result against your desired objects so I assume that's not what was intended

Are you saying theres already a way to check one specific shape against a ray or another shape, with crucial details like point of contact, in a way that isnt as convoluted as I described in the initial issue?

AThousandShips commented 3 months ago

Are you saying theres already a way to check one specific shape against a ray or another shape

Internally there is but it's not currently exposed afaik, but the point is that you'd not use the internals beyond doing the check with the ray, the rest of the system isn't built for one-on-one checking, as detailed above

bud11 commented 3 months ago

Internally there is but it's not currently exposed afaik, but the point is that you'd not use the internals beyond doing the check with the ray, the rest of the system isn't built for one-on-one checking, as detailed above

Strange considering methods like OverlapsBody(body) exist for area3D. The desire for specific checks outside of a signal context is evidently something that's been considered, even if in that case it doesnt update immediately and just references an array generated at the start of the frame.

If there absolutely had to be a compromise to fit within godot's existing behavior, Id say why not add monitoring and overlap signals/lists to collisionobject3D (that can detect any other collisionobject3d rather than just bodies or areas) and remove inherited versions, and have both the signals and the overlap list/check methods return more conviniently usable data like contact pos? Or at least a way to fetch that without jumping through hoops and/or doing an entire extra space state operation or two.

Id also propose a monitoring enum instead of a monitoring bool if monitoring in intense detail constantly is expensive. Something like OFF, CHECK, CHECKDETAILED. Or some way to control/tame monitoring without sacrificing usability. That way certain nodes could monitor in a lot of detail.

If it was all structured like that, whitelisting or whatever could just be programmed by the user into signals, no performance problem.

AThousandShips commented 3 months ago

Strange considering methods like OverlapsBody(body) exist for area3D

Exactly it caches it, but that's fundamentally different from doing checks like that, i.e. checking if some random body or shape intersect


But you're missing what I'm saying, I'm not saying you can't do this, I'm saying the space stuff isn't built for it, you just have to do it in the shapes directly, the physics engine deals with the whole space and tracks things in a spatial index

This is how it works the ideas you're suggesting just doesn't work with the way physics engines are designed in this regard

But you don't need it, you can just check against the shape itself (again, when that's exposed), the methods in the physics space is to handle thousands of objects and therefore doesn't deal in single cases

I think you're not quite grasping the distinction I'm trying to make there I'm sorry if I'm being unclear, there's two cases:

What you can't do efficiently is check against a large-ish subset, which is too large for individual checks to be good for performance, but too small to be relevant for the full space, that's why the index is there in the first place, and where layers come in

Having separate indexes for different groups would take a lot of extra processing, and cost performance each time any of the objects move, etc.

bud11 commented 3 months ago

You can do individual operations, with pretty good performance, but you have to check them individually, you don't get any benefit from the space stuff

Okay, so if I understand, there isnt an actual need or benefit, both from a user perspective and an engine design perspective, for the space state, unless you're operating on lots of objects. But, theres also no exposed way to run specific detailed generalized overlap tests between two shapes (or a ray and a shape) without using the space state like the code in the initial issue does. Which is pretty much what I was getting at with the issue. Though I didnt really understand what the space state was or that it was indexed in an optimized way like this, so thanks for clearing some of that up.

What you can't do efficiently is check against a large-ish subset, which is too large for individual checks to be good for performance, but too small to be relevant for the full space, that's why the index is there in the first place, and where layers come in

Makes sense. Though by whitelist I meant something relatively small, something like 5-20 shapes ever, so just iterating over manual overlap/ray tests would be fine if they were exposed. I think that'd line up with a few other use cases as well. Ive seen other people wanting this kind of functionality for enemy/hitbox/character collisions and for vr hand controls. Not the kind of thing that usually needs to be hyper optimized.

AThousandShips commented 3 months ago

That's correct, it's be useful to expose checking methods to check against shapes, and perhaps helper methods to handle objects entire (with their respective shapes) and also handle the culling, i.e. first check against the combined bounds, then each shape bound, and only then the complex shape itself, that would be relevant for performance