bjornbytes / lovr

Lua Virtual Reality Framework
https://lovr.org
MIT License
2k stars 138 forks source link

Physics API Thoughts #736

Closed bjornbytes closed 6 months ago

bjornbytes commented 10 months ago

With #735 open and high likelihood that LÖVR will switch to Jolt soon, we should start thinking about ways that the Lua API for lovr.physics can be improved! The goals would be to A) make it easier to use, B) make it align better with Jolt. This issue can serve as a place to collect all of the ideas.

bjornbytes commented 10 months ago
xiejiangzhi commented 10 months ago

When an API-friendly but incomplete physics engine is put in front of me versus an API-unfriendly but fully-featured physics engine, I'd rather use the one that's fully functional but API-unfriendly. Because the former can directly complete the task, while the latter, although good-looking, cannot complete many tasks.

On love2d, it can basically achieve what box2d can do, at least the content in the box2d document can be achieved. I just hope that lovr can also try its best to output the complete functions of the physics engine, instead of just making the API simple and beautiful.

jmiskovic commented 10 months ago

@xiejiangzhi I also don't want a toy engine. Jolt is still growing and we will add more features along the way. We should still take this opportunity to remove some of accumulated complexity to make the API more orthogonal and tidier.

@bjornbytes

While we are changing the API, we should also adapt the querying. The jolt integration supports full shape casting (any shape, shape moving through the space).

I would suggest to keep just one raycasting function, having three is confusing.

The biggest hurdle is multiple shapes per body. Jolt supports them through compound shape. We have few options:

  1. Keep the existing Collider:addShape API, with one compound shape to each collider
  2. Adapt to jolt API - expose the CompoundShape and CompoundShape:addShape in lovr API; rework the Collider API
  3. Nudge users to weld the shapes together (with either the FixedJoint, or by converting them to a single mesh shape)

Sometime in the future we could add more features:

bjornbytes commented 10 months ago
  • The inertia seems to be doable through the MotionProperties.SetInverseInertia .

Thanks, didn't know about this.

While we are changing the API, we should also adapt the querying. The jolt integration supports full shape casting (any shape, shape moving through the space).

Yes, would definitely like shape querying and shape casting!

I would suggest to keep just one raycasting function, having three is confusing.

They're pretty convenient and efficient when you're just trying to find the closest shape, which is common. And it would be good to have some way to hook into Jolt's "early out" params for casts, though I haven't wrapped my head around them yet...

The biggest hurdle is multiple shapes per body. Jolt supports them through compound shape.

Yeah this seems complicated and important. Another related issue I noticed is that the Jolt shapes are immutable (can't change radius/size/etc. of shapes once they're created).

There's been some discussion in chat (1, 2) around how multiple shapes is kind of annoying. Merging shapes and colliders somehow would be pretty cool, gotta think about it more.

CompoundShape seems good...it might require you to do if shape:type() == 'CompoundShape' and use recursion in places (e.g. drawing colliders in phywire), but that's probably not a dealbreaker.


I came up with a few more small things while reading through the PR today:

bjornbytes commented 10 months ago

So far I'm kinda leaning away from the CompoundShape idea as well as the merged BoxCollider/SphereCollider/CompoundCollider/etc. object.

For CompoundCollider you still need a way to add/remove shapes and get/set their properties, and Shape objects are best for that. And if you're going to have Shape objects anyway, you may as well separate them from the Collider object so that all the methods are in one place, you can reassign a Shape to a collider, etc.

For CompoundShape, it's nothing more than a list of shapes with per-shape transforms. I think it's friendlier for LÖVR to manage this for you internally, rather than having this weird "shape that's not a shape" object. So basically, current API is pretty good... Unless I just have stockholm syndrome or something.

The implementation for Jolt is kind of annoying. We would have to switch between CompoundShape depending on number of attached shapes, maybe recreate shapes or add "decorator" shapes if some of their properties get modified. Are there any reasons it couldn't work though?

jmiskovic commented 10 months ago

Yes, we can continue with the current API around Colliders and Shapes. We can also test using the CompoundShape even for one-shape colliders and see if we lose any performance. It would simplify things. But the switching between shape primitives and compound shapes is also doable.

Re:

Back to raycasting for a moment. I said before that the physics_jolt.c doesn't stop at endpoint but taking another look at Jolt's side, the ray takes into account the direction vector length and correctly stops at endpoint. I believe lovr always uses the term "direction" as a normalized vector (I found Quat(Vec3), Curve:getTangent(), World:getLocal/WorldVector()). Using "direction" for directed distance vector in this case would be a bit confusing IMO. I would prefer to use the "endpoint" for both the raycast and spherecast because it better captures the intention.

All cast functions should also accept an optional tag parameter, currently only two variants have it. The result of raycastAny is not deterministic, it would have to be quite a bit faster than raycastClosest to earn its place.

The returning from casting early feature is a tad too complicated for me, with the C++ -> C -> Lua callback -> C -> C++ roundtrip. I would rather we collect all results and instead provide the guarantee to users that they are sorted. We can still limit the number of results with tags and the short casting distance, and the raycastClosest variant. What do you think?

bjornbytes commented 9 months ago

From chat:

bjornbytes commented 8 months ago

Here's my up-to-date list:

bjornbytes commented 8 months ago

I did a deeper dive on queries and came up with the following design:

With args:

Notes:

bjornbytes commented 7 months ago

A simpler raycast API would be:

collider, x, y, z, nx, ny, nz, shape = World:raycast(start, end, filter)
World:raycast(start, end, filter, function(collider, x, y, z, nx, ny, nz, shape) end)

Thought about doing a "hit fraction" system but it's too complicated. It does allow for a "custom closest hit" type thing but I think it's okay to omit this.

This way, we can get away with having only a single raycast function.

bjornbytes commented 7 months ago

Simplifying further (similar to Josip's previous suggestions)

That would leave us with the following for all queries:

World:raycast(origin, direction, filter, callback)
World:shapecast(shape, position, scale, orientation, direction, filter, callback)
World:queryBox(position, size, orientation, filter, callback)
World:querySphere(position, radius, filter, callback)

It's nice and compact, but I hope it's not too confusing to document all the "tricks" that are supported.

bjornbytes commented 7 months ago

From chat: we should add degree of freedom restriction. There's SixDOF joint, but a simpler approach might be the allowedDofs param of MotionProperties::SetMassProperties (world space only though).

Example API might be Collider:setLockedAxes([translation], [rotation]) where each one can be strings with "xyz" chars.

bjornbytes commented 6 months ago

Ok pretty much all of the ideas here have been implemented, so I'm going to close this.