C# Равномерное круговое движение

Мне нужно реализовать простую анимацию шара, движущегося равномерным круговым движением. Я пробовал несколько формул, и следующая версия кажется лучшей. Однако, есть еще 2 проблемы, и я действительно не могу понять, что не так. Во-первых, через пару секунд сразу после запуска программы мяч движется хаотично. Я думаю, что значения для тета (угол в радианах) не рассчитаны правильно, но я не знаю почему. Во-вторых, через некоторое время движение становится более равномерным, но со временем оно уменьшается. Значение "скорость" указывает количество секунд, необходимое для полного оборота. То, что я хочу, - это равномерное, правильное круговое движение (в соответствии со значением скорости) и без рывков в начале.

Мой код до сих пор:

public partial class ServerForm : Form
{
    Stopwatch watch;

    //Angular velocity
    float angularVelocity;

    //Angle
    float theta = 20;

    //Speed - time to complete a full revolution, in seconds
    private float speed = 3;

    //Circle center
    private int centerX = 250;
    private int centerY = 200;

    //Circle radius
    private float R = 120;

    //Current position
    private LocationData currentLocation;

    public ServerForm()
    {
        InitializeComponent();
    }

    public void UpdateUI()
    {            
        currentLocation.CoordX = (float)(centerX + Math.Cos(theta) * R);
        currentLocation.CoordY = (float)(centerY + Math.Sin(theta) * R);
        currentLocation.Speed = speed;

        try
        {
            this.Invoke(new Action(() => { this.Invalidate(); }));
        }
        catch (Exception ex)
        {
            watch.Stop();
            Application.Exit();
        }

        theta += (float)((angularVelocity * 1000 / watch.ElapsedMilliseconds));            
        //Console.Out.WriteLine("elapsed miliseconds: " + watch.ElapsedMilliseconds + " theta = " + theta);

    }

    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        Brush color = new SolidBrush(Color.BlueViolet);

        g.FillEllipse(color, currentLocation.CoordX, currentLocation.CoordY, 30, 30);

        //Draw circle & center
        g.DrawEllipse(new Pen(color), centerX, centerY, 5, 5);

        float x = centerX - R;
        float y = centerY - R;
        float width = 2 * R;
        float height = 2 * R;
        g.DrawEllipse(new Pen(color), x, y, width, height);

        base.OnPaint(e);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        if (!String.IsNullOrEmpty(textSpeed.Text))
        {
            ResetValues(float.Parse(textSpeed.Text));                
        }
    }

    private void ResetValues(float newSpeed)
    {
        speed = newSpeed;
        angularVelocity = (float)(2 * Math.PI / speed);  // radians / sec

        //Start at the top
        currentLocation.CoordX = centerX;
        currentLocation.CoordY = centerY - R;

        theta = 90;

        watch.Restart();            
    }

    private void ServerForm_Load(object sender, EventArgs e)
    {
        watch = new Stopwatch();            

        timer1.Enabled = true;
        timer1.Interval = 100;
        timer1.Tick += timer1_Tick;            

        currentLocation = new LocationData();
        ResetValues(speed);          
    }

    void timer1_Tick(object sender, EventArgs e)
    {
        UpdateUI();
    }

}

LocationData - это просто класс, содержащий координаты и текущую скорость. Правильны ли единицы измерения времени и угловой скорости (и преобразования в миллисекундах)?

ОБНОВЛЕНИЕ: изменил BackgroundWorker на Timer, но я все еще получаю это беспорядочное движение, и через некоторое время движение замедляется.

1 ответ

Решение

Попробуйте использовать System.Windows.Forms.Timer вместо BackgroundWorker. Я верю, что вы получите более последовательные результаты. Это определенно не хороший случай для использования BackgroundWorker.

Вот более-менее полное решение. Обратите внимание, что я масштабирую радиус качания и радиус шара по размеру формы.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        SetStyle(ControlStyles.Opaque, true);
        SetStyle(ControlStyles.ResizeRedraw, true);
        SetStyle(ControlStyles.DoubleBuffer, true);
        SetStyle(ControlStyles.UserPaint, true);
    }

    private void Form1_Load(object sender, System.EventArgs e)
    {
        _stopwatch.Start();
    }

    private void Timer1_Tick(object sender, System.EventArgs e)
    {
        Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.Clear(BackColor);

        const float rotationTime = 2000f;

        var elapsedTime = (float) _stopwatch.ElapsedMilliseconds;

        var swingRadius = Math.Min(ClientSize.Width, ClientSize.Height) / 4f;

        var theta = Math.PI * 2f * elapsedTime / rotationTime;

        var ballRadius = Math.Min(ClientSize.Width, ClientSize.Height) / 10f;

        var ballCenterX = (float) ((ClientSize.Width / 2f) + (swingRadius * Math.Cos(theta)));

        var ballCenterY = (float) ((ClientSize.Height / 2f) + (swingRadius * Math.Sin(theta)));

        var ballLeft = ballCenterX - ballRadius;

        var ballTop = ballCenterY - ballRadius;

        var ballWidth = ballRadius * 2f;

        var ballHeight = ballRadius * 2f;

        e.Graphics.FillEllipse(Brushes.Red, ballLeft, ballTop, ballWidth, ballHeight);

        e.Graphics.DrawEllipse(Pens.Black, ballLeft, ballTop, ballWidth, ballHeight);
    }

    private readonly Stopwatch _stopwatch = new Stopwatch();
}
Другие вопросы по тегам