Inheritance / Interface Solutions for Physical Engines

This is for a small game project with SDL on MinGW / Windows.

I am working on a physical engine, and my idea was to have a Physics::Object from which all physical objects should be extracted, and it is registered using the Physics::System global class (this is a monostat template) so that the user does not need to track which objects are included in physical calculations, and you just need to call a function like Physics::System::PerformTimestepCalculation(double dt) .

This works great, and I even implemented it using one derived Physics::Circle class, which is the 2nd circle. I was pleased with the predictive collision detection, although I still need to optimize it.

In any case, I had problems when I started adding other primitives for inclusion in the calculation, for example. line. Physics::System::PerformTimestepCalculation(double dt) was littered with calls to Object::GetID() or similar functions (can dynamic_cast <> escape), but I feel dirty.

I did a little reading and realized that my elements of my hierarchy are not replaceable (i.e., the collision between two circles is very different between the collision of two lines).

I like my own Physics::Objects "own register" with the System class, so they are automatically included in the calculations, and I really do not want to lose this.

There must be other reasonable design paths. How can I better remake things so that non-replaceable objects do not interfere?

Edit FYI: In the end, I separated the properties of the entity and the form, similar to how it was described in the accepted answer, and similarly to the model of the entity-component-system. This means that I still have the yuk logic “is it a circle or a line, and is it a line or a circle?”, But I no longer pretend that polymorphism helps me here. It also means that I use some kind of factory and can have several computing worlds at once!

+6
source share
2 answers

The most successful publicly accessible physical engines are not very difficult for "templates" or "object-oriented design."

Here's a summary of my admittedly bold statement:

Chipmunk - written in C, enough is said.

Box2d - written in C ++, and there is some polymorphism here. there is a hierarchy of forms (b2Shape base class) with several virtual functions. However, this abstraction seeps in like a sieve, and you will find many castings to leaf through classes throughout the source code. There is also a hierarchy of “contacts”, which turns out to be more successful, although with a single virtual function it would be trivial to rewrite this without polymorphism (as I suppose, a pointer to a function is used for a tweet). b2Body is a class used to represent solids, and it is not virtual.

Bullet - Written in C ++, used in a ton of games. Tons of functions, tons of code (relative to the other two). Actually there is a base class that extends to bodies with a solid body and a soft body, but only a small part of the code can use it. Most of the virtual function of the base class relates to serialization (saving / loading the state of the kernel), of the two remaining virtual functions, the soft body cannot implement one with TODO, informing us that some hacks need to be cleared. Not quite the sound of approval for polymorphism in physical engines.

These are many words, and I did not even begin to answer your question. All I want to score is that polymorphism is not something that is effectively applied to existing physical engines. And this is probably not because the authors did not “receive” OO.

One way or another, my advice is: polina is watered for your entity class. You will not have 100 different types that you cannot reorganize later, your data on the physical engine will be quite uniform (convex policies, boxes, spheres, etc.), and the data of your entity will probably be even more uniform (maybe , only solids to get started).

Another mistake that I feel that you are doing only supports physics :: System. There is a utility in modeling bodies independently of each other (for example, for playing with two players), and the easiest way to do this is to support several physical :: systems.

With this in mind, the cleanest “pattern” to follow is the factory pattern. When users want to create a rigid body, they need to tell the :: System physics (acting like a factory) to do this for them, so in your physics :: System:

 // returning a smart pointer would not be unreasonable, but I'm returning a raw pointer for simplicity: rigid_body_t* AddBody( body_params_t const& body_params ); 

And in the client code:

 circle_params_t circle(1.f /*radius*/); body_params_t b( 1.f /*mass*/, &circle /*shape params*/, xform /*system transform*/ ); rigid_body_t* body = physics_system.AddBody( b ); 

Anyhoo, a kind of rant. Hope this is helpful. At least I want to point you to box2d. It is written in a fairly simple C ++ dialect and the templates used in it will be relevant to your engine, whether 3D or 2D.

+5
source

The problem with hierarchies is that they do not always make sense, and trying to cut everything into a hierarchy simply leads to inconvenient decisions and disappointments along the line.

Another solution that can be used is a tagged union, best embodied in boost::variant .

The idea is to create an object that can hold one instance of this type (among a pre-selected list) at any given time:

 typedef boost::variant<Ellipsis, Polygon, Blob> Shape; 

And then you can provide the functionality by switching the list of types:

 struct AreaComputer: boost::static_visitor<double> { template <typename T> double operator()(T const& e) { return area(a); } }; void area(Shape const& s) { AreaComputer ac; return boost::apply_visitor(s, ac); } 

The performance is the same as virtual dispatch (not so much, usually), but you get more flexibility:

 void func(boost::variant<Ellipsis, Blob> const& eb); void bar(boost::variant<Ellipsis, Polygon> const& ep); // ... 

You can provide functions only when necessary.

And on the issue of binary visit:

 struct CollisionComputer: boost::static_visitor<CollisionResult> { CollisionResult operator()(Circle const& left, Circle const& right); CollisionResult operator()(Line const& left, Line const& right); CollisionResult operator()(Circle const& left, Line const& right); CollisionResult operator()(Line const& left, Circle const& right); }; CollisionResult collide(Shape const& left, Shape const& right) { return boost::apply_visitor(CollisionComputer(), left, right); } 
+3
source

Source: https://habr.com/ru/post/908762/


All Articles