Very convincing, specially the second purpose (for my case). Where can I get the code, with the callback fix?
Always here:
http://sourceforge.net/projects/enjomitchsorbit/
You can go to Code -> SVN Browse and from there you can Download GNU Tarball, if you don't use Tortoise SVN that is. On the main page, there's also a downloadable html documantation. Beware - it's LGPLed. so you either share your code, or link the lib dynamically. I'll be preparing a dynamic lib later this week. Until then (whenever it happens), you can experiment with it as you like. The reason why I'm licensing it this way is that I want people to at least mention that they use my and Computerex' work while having multiple vessels support, so others can have it too.
Again, very convincing. I'll make it a PD controller, using your code. Later some day I'll check optimal control theory
.
I forgot to mention, that you need to limit the control signal somehow, that the resulting
needMod is always in range <-1, 1> by dividing it by some maxAccel, obtained as a ship's characteristic, through oapi. Notice that we're not steering by velocity here, but acceleration.
I don't remember how I calculated it for my controllers, but I've found this in FuzzyControllerAP:
Code:
double FuzzyControllerAP::m_statDeltaGliderRefRotAcc = 5000.0 / 11000.0; // Yaw thrust / empty mass
So you need to get thrust of your rotation group. You could use:
Code:
DWORD GetGroupThrusterCount (THGROUP_TYPE thgt) const
THRUSTER_HANDLE GetGroupThruster (THGROUP_TYPE thgt, DWORD idx) const
I checked the PidMFD code. Pid.cpp shows that two old error values are kept in memory (e1 = error[n-1] and e2 = error[n-2]), but they are not used I think. Rather, the integral value is calculated simply as error*timeStepDuration (not really an integration right?).
I don't like the idea of using error values from previous iterations, but I can test the integral value error*timeStepDuration. Does this value have any physical meaning?
I agree. It doesn't look like an integration. Integration would be:
Code:
IntegratorDiscrete::IntegratorDiscrete()
{
Reset();
}
void IntegratorDiscrete::Reset()
{
m_integ = m_ePrev = 0;
}
double IntegratorDiscrete::GetIntegral()
{
return m_integ;
}
double Integrator::IntegrateTrapezoidal(double e, double dt) {
m_integ += (e + m_ePrev) * dt / 2.0
m_ePrev = e;
return m_integ;
}
... based on equation for area of trapezoid:
http://pl.wikipedia.org/wiki/Trapez#Trapez_prostok.C4.85tny
Rotate it 90* left, and it will resemble your error in time. So actually the code would look like:
Code:
double Ki = something;
double needMod = Kp * error +
Kd * m_currentAngularVelocity +
Ki * m_integrator.IntegrateTrapezoidal(error, dt);
[EDIT] It's possible, that your Kd should be negative, because the faster you are proceeding, the lesser the power should be. Noticed any similarities to your code?
You assumed there that Kd = -1. Above you have a more formal controller's description. Even more formally, the Kd should stay positive when you apply an assumption, that positive velocity means moving away from target and negative means towards target.
And, finally, a question that must be understood as a product of the late hour: why not a PID2D (2D = second derivate) controller?
I don't know. Keep it simple. PIDs are widely used. I've never heard of PID2D.
Ops, another thing. In addition to the error calculation function, I can think of a second:
Code:
double error = length(r_current->getThrustVector() - m_currentDirection);
double error = asin(length(cross));
Would the second line be against the PID controller design? The second error value is actually the angle difference between
r_current->getThrustVector() and
m_currentDirection, so I think it's more adequate.
No, it would definitely not be against the PID design, if it works the way it's supposed to work. What's needed is a linear (I think) decrease of the error value towards 0, as you reach your desired orientation. In fact, if the code works, it gives you an opportunity to limit the maximal error up to +/- 180*. It's an interesting idea, since you already operate on one axis, thanks to SetAttitudeRotLevel(VECTOR3 &). One problem would be that you use
asin, which is limited to (-PI/2, PI/2). You want (-PI, PI) to properly operate on all quarts. Therefore
atan2 is usually preferred.
To get an angle between 2 vectors, in Launch MFD I do the following:
Code:
double VectorMath::dot(const Vect3 & a, const Vect3 & b )
{
return a.x*b.x + b.y*a.y + a.z*b.z;
}
Vect3 VectorMath::cross(const Vect3 & a, const Vect3 & b )
{
Vect3 v;
v.x = a.y*b.z - b.y*a.z;
v.y = a.z*b.x - b.z*a.x;
v.z = a.x*b.y - b.x*a.y;
return v;
}
double VectorMath::angle(const Vect3 & a, const Vect3 & b )
{
Vect3 aNorm = a.norm();
Vect3 bNorm = b.norm();
return atan2( cross(aNorm, bNorm).len(), dot(aNorm, bNorm) );
}
double Vect3::len() const
{
return sqrt(x * x + y * y + z * z );
}
Vect3 Vect3::norm() const
{
return Vect3(*this) /= len();
}
The (reusable) classes for that are in Math and Systems folders. You'd only need to make a converter from VECTOR3 to my Vect3 and vice versa. You can mimic the SystemsConverter in Systems for that.
PS.
Here's another thread I've found about PID.