问题
I'm moving through the atmosphere with Microsoft Virtual Earth 3D and I can descend smoothly, but I don't know the math to ascend smoothly.
I'm descending like this:
for(int curAlt = startAlt; curAlt < endAlt; curAlt--){
//do something
curAlt -= curAlt/150
}
This works by decreasing the size of the jump the closer I get to the earth (lower altitude). I need a solution that would do similar, just in reverse, while still keeping the smaller jumps at the lower altitude.
How can I do this? Or is what I am doing unacceptable and should be done differently (say with logarithms)?
回答1:
An even better solution might be to use a function like the Logistic function.
Double minAlt = 0.0;
Double maxAlt = 500000.0;
Int32 numberSteps = 1000;
Double boundary = +6.0;
for (Int32 step = 0; step < numberSteps; step++)
{
Double t = -boundary + 2.0 * boundary * step / (numberSteps - 1);
Double correction = 1.0 / (1.0 + Math.Exp(Math.Abs(boundary)));
Double value = 1.0 / (1.0 + Math.Exp(-t));
Double correctedValue = (value - correction) / (1.0 - 2.0 * correction);
Double curAlt = correctedValue * (maxAlt - minAlt) + minAlt;
}
Because the current altitude is explicitly calculated you do not have to rely on a iterative calculation introducing all sorts of precision releated errors.
See the sample code for how to tune the function shape.
Here is a sample console application that displays the function. You can play a bit with the parameters to get a feeling for the behavior.
using System;
namespace LogisticFunction
{
class Program
{
static void Main(string[] args)
{
Double minAlt = 5.0;
Double maxAlt = 95.0;
Int32 numberSteps = 60;
// Keep maxAlt and numberSteps small if you don't want a giant console window.
Console.SetWindowSize((Int32)maxAlt + 12, numberSteps + 1);
// Positive values produce ascending functions.
// Negative values produce descending functions.
// Values with smaller magnitude produce more linear functions.
// Values with larger magnitude produce more step like functions.
// Zero causes an error.
// Try for example +1.0, +6.0, +20.0 and -1.0, -6.0, -20.0
Double boundary = +6.0;
for (Int32 step = 0; step < numberSteps; step++)
{
Double t = -boundary + 2.0 * boundary * step / (numberSteps - 1);
Double correction = 1.0 / (1.0 + Math.Exp(Math.Abs(boundary)));
Double value = 1.0 / (1.0 + Math.Exp(-t));
Double correctedValue = (value - correction) / (1.0 - 2.0 * correction);
Double curAlt = correctedValue * (maxAlt - minAlt) + minAlt;
Console.WriteLine(String.Format("{0, 10:N4} {1}", curAlt, new String('#', (Int32)Math.Round(curAlt))));
}
Console.ReadLine();
}
}
}
回答2:
By the way, you should really make the ascension time dependent (framerate aware). All the answers here depend on the code being called at a specific interval; which it is not. If some process kicks in, Virtual Earth comes under stress somehow, if you minimize Virtual Earth, or if something that affects the performance of Virtual Earth happens the movement will be anything but smooth. Even if 'nothing' happens to Virtual Earth sometimes your 3D card will stall which means potentially you will get a jump every once in a while.
In particular if the user has VSync turned off you will get some really nasty things cropping up:
- On slow machine the ascension will take forever (even with VSync on).
- On fast machines it will be so fast you might not even notice it.
In your class:
private int lastTime;
In your loop/event:
if(lastTime == 0)
{
lastTime = Environment.TickCount;
return;
}
int curTime = Environment.TickCount; // store this baby.
int timeDiff = lastTime - curTime;
if(timeDiff == 0)
return;
curAlt += (maxAlt - curAlt) * timeDiff / (150000); // TickCount reports
// time in Ticks
// (1000 ticks per second)
lastTime = curTime;
If you want to get fancy you could plug the code from the DX SDK. Environment.TickCount has a resolution of 15ms (reason I check for the timeDiff being zero, because it could easily be). The managed DX SDK sample framework has a class called DxTimer (or sorts) that has a better resolution.
There is an article that uses the same API.
回答3:
It depends on what you want to achive by smoothly ascending. You could limit the altitude to some maximum value maxAlt and approach that value smoothly in the same way as you do with the ground.
curAlt += (maxAlt - curAlt) / 150
But if the maximum altitude is unbounded, you have to clearify what you exactly want to be smooth.
Also note that your code works only by some side effects. You are close to an infinite loop. I would suggest the folowing.
epsilon = 0.1; // A small value that fits your needs
curAlt = startAlt;
while (curAlt > endAlt + epsilon)
{
curAlt -= (curAlt - endAlt) / 150;
}
The iteration of curAlt -= (curAlt - endAlt) / 150 will never reach endAlt in theory and in real at most by rouding errors. Your code only works because you subtract one additional height unit per iteration. I am not sure if this is by design or a error that prevents a bug. Adding the epsilon threshold breaks the loop in a more logical way.
回答4:
Since you use curAlt -= curAlt/150 for decending, why not use curAlt += curAlt*150 for ascending?
来源:https://stackoverflow.com/questions/706952/smooth-movement-to-ascend-through-the-atmosphere