godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.17k stars 98 forks source link

Add a built-in PID controller class #11043

Open TrogerMester opened 4 weeks ago

TrogerMester commented 4 weeks ago

Describe the project you are working on

2D and 3D test projects for RigidBody based locomotion and other types of RigidBody movements.

Describe the problem or limitation you are having in your project

I find myself constantly creating or downloading a plugin for implemention of a simple PID Controller which can be used for so many things in so many of my projects. I am also concerned with the performance of such implementations since these are mostly written in GDScript, not even C#.

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

Many times in game development you need to control a value each frame at runtime, dynamically reacting to changes which cannot be easily predetermined. Therefore the use of a generic PID Controller comes to mind. Sadly in Godot, there's no such an Object in the engine out of the box.

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

Basically, a RefCounted derived class which you can store in a variable inside your class and initialize it with desired P, I, D coefficients which are public members of the PID Controller so you can access them, and possibly change them at runtime. Has a calculate(double target_value, double current_value, double delta) function to get an input for your system. This calculate function has overloads for Vector2, Vector3 parameters and also has calculate_angle() function which has the same parameters and overloads as the calculate() function but with respect to wrapping around PI for max ease of use.

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

It can be worked around pretty easily by just getting a plugin for it or copy pasting an already done PID Controller implemention from somewhere on the web (sometimes with a few modifications if it's not written in your desired scripting language). But since this concept is so widely used, the user shouldn't have to copy paste or download the same plugin for all his projects every time.

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

Performance. C++ code runs much, much faster than GDScript code especially for calculations. Many nodes may use PID Controllers in your scene EACH FRAME which can tank performance pretty easily. The thing is, it's such a generic tool that adding it would benefit everyone and it's not super specific to some projects either. I mentioned how I use it for movement and rotation for RigidBodies but PID Controllers can be used for basically any value in your game which needs to be controlled dynamically.

Calinou commented 4 weeks ago

Out of curiosity, what are some concrete use cases for PID controllers in game development? Are they only used during the prototyping stage, or are they also used in production? I looked at Wikipedia and I have trouble understanding it all. It looks like a fancy interpolator to me, but surely there's more to it...

sievaxx commented 3 weeks ago

It looks like a fancy interpolator to me, but surely there's more to it...

A PID is a second-order system, and an adaptive way of reaching a target value. You typically apply them to higher order of dynamics, like the linear or angular velocity of an object. The main way you would try to configure them is to find settings that hopefully smoothly reach your goal, against anything that could influence it. This is good for anything that uses forces, like physics objects, which can be influenced by gravity and drag and constraints.

A good example here would be anything that flies, like a drone. Since you can only control a drone through thrust, and it has to apply enough force to keep itself up in the air against gravity, you can only really modify how much it accelerates and decelerates (in its local frame) to move it. You also have to account for its own mass as to not under or overshoot reaching its target when accelerating and decelerating. A PID makes solving both of those to get to a target location an incredibly simple thing.

A PID could be used as a "fancy interpolator", but making a specific second-order system designed for that purpose would be better. An example is this video "Giving Personality to Procedural Animations using Math".

Zireael07 commented 3 weeks ago

PID controllers are often used for car simulators

mechalynx commented 1 week ago

I came to see if there was a proposal for this already since I just finished implementing a PID controller in gdscript to solve an issue I was having. While doing so and refactoring the class that's supposed to use it, I realised it was such a useful yet generic class to have around, that it would make a great fit for a built-in.

I think I'll use the existing proposal format for the rest, as it might be a bit disorienting to have 2 comments with that format but I feel it'll make what I'm saying clearer to structure it that way.


Describe the project you're working on

Anchoring a RigidBody3D in an arbitrary location in space and aligning its rotation to the node it is anchored to. It has to be able to still experience forces and be able to rotate but still attempt to correct its position and rotation, within the holding capability of the anchor node.

Describe the problem or limitation you are having in your project

Not being aware of PID controllers, I attempted to implement a very similar solution myself through a lot of trial and error. I implemented a pulling force and damping force to counteract it, for the linear motion. Eventually as I was struggling to figure out why rotational alignment was unstable, though suspecting the moment of inertia was somehow off on the RigidBody (it should be (0, 0, 0) but it was (0.1, 0.1, 0.1) - not sure if this is a bug), I searched and found out PID controllers are commonly used for such tasks. My rotation issue likely requires a factor equivalent to the integration component of a PID controller.

I implemented my own class for this based on what I discovered but in doing so I felt that it would be much safer to have this be running in native code, as just for one object it would have to calculate 6 floats, 3 for each Vector3 (position and rotation) and do so on every physics frame. Additionally, it would make gdscript code more robust if we had overloaded versions of the main calculation function, which is not possible in gdscript but is possible using classes written in c++.

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

A PID controller is naturally suited to tasks such as the one I'm trying to implement, which is to use forces to smoothly position a RigidBody3D and align it using forces.

Having an available class for this that simply runs the same calculations quickly and is available as a built-in would make the friction of implementing a wide range of such behaviors very low, reducing it to simply fine-tuning the parameters to fit the intended behavior. (See "Use cases" below)

The way I wrote my anchor node currently requires the PID controller class script to also be included first. While this isn't rare for projects, it would allow nodes that otherwise aren't doing a particularly complex task as their end behavior to be free of the extra inclusion. For example my anchor class could simply be included by itself, without having to remember to include the PID controller class as well. Said class is likely to be useful to others as well and the usability of it would be better if users did not have to get a script and another dependency. None of this is going to ruin my project if not available - I mention it as a general benefit that, if a PID Controller class was accepted as useful, it has extra benefits that would reduce friction across the board for projects that use them.

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

For details on what a PID controller is when implemented for game use, I'll link this article which is how (and apparently others) figured it out, as it's written pretty cleanly with visual examples: (https://vazgriz.com/621/pid-controllers/)

For an example of implementaion in gdscript, you can look at these (in no particular order):

As in the OP, I agree with everything there, as it is how I'm doing it myself and what every implementation I found was doing. As a detail, I would prefer having the arguments in the reverse order to match having delta first just like the _physics_process function has them, for consistency. Having the default function work with float and having overloads for Vector2 and Vector3 I think is the most sensible approach, as typically we're either trying to control one value or transforms (position, rotation) in space, 2D or 3D or just 2 components of a 3D transform.

My implementation in gdscript is capable of maintaining internal state for a linear calculation along one dimension, an angular calculation along one dimension and a Vector3 calculation for linear and rotational cases all at once by maintaining internal state for all of these separately. So this way I only need one controller. However, whether a core implementation of such a controller should maintain all this state is debatable. I don't think it'd be a big issue when it comes to memory because we are just storing a few extra floats and if we're trying to control both linear and angular values, we save on PID Controller allocations while still using the same memory for the calculations themselves. The standard implementation keeps track of internal state as a necessity since it needs to know rates of change and accumulated error. Which is also why a reset() method is needed for when we change the context in which we are using the controller (so we reset its internal memory to avoid glithes or having to allocate a new one).

If a user wishes to calculate something else, it will likely be castable to a float or they'd be able to do extra dimensions separately (since the Vector overloads would, in the currently common implementations, just calculate each dimension separately), at the cost of using more than one PID controller. Using more than one controller seems common for more complex entities and such controllers do not assume anything about the context they're used in (other than its internal memory implicitly assuming context hasn't changed), making it possible to use them for things other than positions and rotations. This would of course risk performance issues if enough of these controllers were running at the same time, which is part of the advantage of core inclusion (or gdextension implementation of course).

In theory, one could make a PID controller that has an arbitrary and configurable set of internal dimensions but that would make using it for the most common applications more complicated and harder to understand. Also it would add little to its potential. So just having the base and overloaded versions of the calculation function should be good enough to accomodate the majority of cases without making internal code or the interface unnecessarily complex.

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

As the OP says, it can be worked around simply by copying an implementation off the internet, getting a plugin or re-implementing it in gdscript (or c#). For reference, my current PID controller implementation in gdscript is around 200 lines if we exclude comments and it is made to avoid repeating code as much as possible (though mine does handle both single float, Vector3 and angular versions - most implementations simply focus on handling just float values and the smallest I've seen was 45 lines in gdscript, though the length of each execution path is likely similar in mine as with that one).

However, as the OP also says, it seems so commonplace for people doing certain things that it would surprise me not at all if an engine with physics already had such a thing. However, to my knowledge, neither Unity or UE have a PID controller equivalent built-in. I'm not trying to compare them to Godot, I mention this just to be transparent about the fact that it doesn't seem to be something game engines decide to include as a built-in, just so there isn't any assumption they do (though I don't think that should decide the matter here).

It is true that no matter what we do, each of us will have certain scripts we copy over to new projects, or use custom script templates for the same purpose. While it is unavoidable, it would make sense to have certain classes like this, that are generic and can find widespread use, be built-in so as to cut down on this need.

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

While it is not necessary for every project (and many other nodes aren't, such as the Skeleton nodes) and one can definitely make an add-on, either in gdscript or gdextension (if performance is a concern), it seems to me it is far more generic and drop-in than even character controllers. Its value would be equivalent to something like the AStar calculation classes. Multiplayer can also be handled with an add-on and there are plenty, yet it is also in godot as a set of built-ins.

Implementations of PID controllers are so consistent with each other in both internal calculations and interface, that one could most likely simply use a built-in version of a PID controller as a drop-in replacement for their own scripted custom version, adjust function calls to match the signatures and internal parameters (which are the same across implementations) and it would most likely work just like before.

In short, while performance can be handled using gdextension, the inclusion in the engine would be beneficial in that an elegant computational solution to a wide array of problems would be available internally. The same argument can be made for other things, such as state machines, and just like with state machines, there is more than one type of PID controller. While state machines do not have a built-in version, we can still wrangle AnimationTree to achieve similar functionality using a built-in and it works, outside of the semantics being wonky. So in cases where state switching happens far too quickly and can cause performance issues if we were doing it in gdscript, using AnimationTree can circumvent that, without requiring any external inclusions, with the incidental benefit of getting a visual representation of the state machine and animations associated with states.

I can simplify the reason for inclusion to: convenience. It would save many projects work, time and trial and error. But is not impossible to do outside the core. It is not however an inclusion that might see use, it is one that will see use and in the form proposed, barring very niche cases where not even extending or combining it would suffice. The problems it solves already exist and it is already being used to solve them. The inclusion of it in the engine is simply a matter of making godot itself accomodate solving such problems directly, instead of having to solve them in the same way per project, with minimal impact to godot's maintenance cost. It isn't that we have a problem and are demanding that godot developlment finds us a solution - we already know the solution, we are proposing that since it is general and simple enough, that it be included in the engine due to its commonplace usage when problems are of a sort it can solve easily.

Use cases

PID controllers are rarely implemented in a way that is specific to the project and are pretty generic, as are their parameters.

As long as one is dealing with a problem where they need to calculate a "force", where what they're moving experiences "momentum" and what they need is to "move" the object without excluding other "force" interactions, toward a "target", the same basic PID controller can solve this problem, likely even with the default values. As long as the problem can be formulated that way, a PID controller is a drop-in solution. Artistic control is still achievable by adjusting parameters to get a slow or fast response, oscillations (if desired) and even follow a curve (whether that is a bezier curve in space or a curve resource) by using its interpolated value as the target. The basic implementation can even deal with moving targets.

You can think of it as the equivalent of IK as opposed to FK but applied to rigid bodies outside bone rigs. Instead of having to calculate all inputs and coming up with a solution to control a rigidbody (equivalent to something like FK), you instead take the output and calculate the input based on an error, while also taking the changing nature of your target into account. With a PID controller, you can do the equivalent of look_at() and lerp() but by using forces on a rigid body, among other cases where such functions interfere with the nature of the problem. Like AStar which has uses outside of pathfinding enemies (such as traversing abstract graphs, which I believe godot does internally with the AnimatonTree if I'm not mistaken), this can be used to solve problems that are outside the scope of controlling rigid bodies with forces.

Possible use cases outside of my own would include:

(fun fact: turrets were my systems theory teacher's favorite example when I was studying for my automation engineering degree)

For additional use cases, here are some links where people have had problems that would be solved by a PID controller:

The above cases also demonstrate that one can get spring-like behavior out of a PID controller with factors chosen appropriately.

It is true that a PID looks like a fancy version of a Tween (I did for a moment think that perhaps I could do the same with a Tween to be honest) or interpolator but in cases where rigid bodies are involved or any problem that has equivalent properties to what a PID solves (momentum, multiple factors influencing the value that is being interpolated), neither a Tween or Animation can fit the job and having to roll one's own can take days of trial and error, most likely resulting, like in my case, writing a PID controller anyway. It's likely that in many cases, people instead "cheat" the behavior instead of doing it the way they want it, because they're not aware of a solution such as the PID controller, perhaps having to sacrifice using rigid bodies or something else.

Concerns

I am unsure if it should be named PID_Controller as although searching for that in the docs, for someone who knows what it is, will immediately yield the expected kind of thing, if someone searches the internet for "PID Controller" they'll likely get a broad range of results, mostly irrelevant to game development, let alone Godot (such as engineering articles or questions related to software errors since "pid" more commonly means "process id"). However, in theory this could apply to something like Skeleton and Bone or Node as well, so I'm uncertain as to whether it's a problem or not. The wikipedia article mentions the alternative name "Three term controller" but that risks a user seeing it and thinking this isn't a PID controller if that's what they were looking for, as the latter name is more commonly used within game development.

Discoverability is a bit of a concern since this would be as easy to find as AStar classes, which I would not be surprised if people don't know are there as independently usable classes and just implement their own anyway. The existence of the navigation system, which is more obvious, resolves this to some extent in the case of AStar but I don't know if this would be in any way true for a PID controller class. It might need to be mentioned in the documentation or have a tutorial article written for it (which I am willing to do) as its use is not obvious due to its generic nature and the terminology used.

Upsides

The implementation is fairly simple to understand and is just calculations, so it is unlikely to break or need lots of maintenance.

float, Vector2 and Vector3 interfaces would find a lot of use but I was thinking if there should be integer versions of these. However the user can simply use int() and float() casts and it should work fine. For something like a Vector4, they would likely have to use another controller for the extra dimension (or use the extra 1D linear dimension interface as in my gdscript implementation) but that does not seem to be that common a use case and isn't a big issue for the user to have to do, since it wouldn't cause a disproportionate increase in calculations.

For cases where, for example, you have a ship that has thrusters in all 6 orthogonal directions, you can still calculate a Vector3 and outside the PID controller, figure out which thrusters should work to achieve the desired direction and total thrust. I am not sure such a case would be as simple as that though, since I have not tried it.

So the upside is basically it can find a very broad range of uses with a fairly simple implementation and interface, requiring the user to adjust behavior minimally to their project, while expecting reliable and tunable behavior on part of the PID controller itself.

Conclusion

Personally, I feel strongly that such a class would make complete sense to add as a built-in, both for availability and performance.

Built-ins should be minimal solutions that save time and effort, be of reasonably general and varied use, perhaps benefit from performance and being avaiable as built-ins, so the user has less of a requiremnt to look outside the engine for a solution. The scope is easy to figure out due to the ample use in both games and beyond, so we know what needs to go in and what might be overengineering. It feels like pretty much a textbook case of what kind of class should be built-in. In my opinion, if AStar makes sense to have available as a core calculation class, so does this.


I was planning on making the same proposal after I had at least a functional c++ implementation to offer as well. As of now, I have only implemented my PID controller in gdscript and I am refactoring the node that is supposed to use it so that I can test it. I have searched through the godot source and I think I can at least code a functional version of it, based on the same format that AStar3D is written in, even though I'm not experienced with C++ and I am unlikely to write it idiomatically. I have made stub versions of the code though and I feel I could at least get most of the work done, with only some adjustments needed for it to be included.

My gdscript implementation is mostly based on getters and setters because I've taken to implementing things that way recently. I'm not sure if doing it that way would work well in c++ or if it is compatible with the existing code base.

I plan on trying to implement it in c++ anyway, so if this seems an acceptable proposal to the maintainers, I can, in time, provide something that works and compiles, I feel. I just can't promise any time since this week is likely to be hectic and I'm still trying to figure out how to properly refactor my own node that is supposed to use it, so I can test it for proper behavior. I'd also need to make a demonstration project, both to validate its behavior and demonstrate what the point of it is. I am investigating variations on the base PID controller in case there's some easy-to-add behavior that might avoid future requests for inclusion (such as self-tuning), though I am not at this point suggesting such behaviors should be part of what is included in core. As it stands, I feel it is a "most use out of least implmenetation" kind of thing, which I think makes the most sense.

I will try to get it done as soon as possible since we are in dev4 of 4.4 and I'd hope to have it included in 4.4 if possible, assuming feature-freeze has not been decided yet.

My apologies for the excessively long comment but I feel strongly that this inclusion in the core engine makes complete sense and would not cost a lot in code or maintenance while saving lots of headaches for users that have the need for it, hence my enthusiasm to back this proposal.