Тригонометрия (или, возможно, ошибка точности?) Проблемы с C # - PullRequest
1 голос
/ 04 сентября 2011

Я пытаюсь создать программу, способную создавать анимацию - рисуя на экране фигурку, позволяя пользователю двигать руками и ногами и так далее.

Но если я хочу переместить деталь (или фигуру), связанную с обоими концами, в другую деталь, мне тоже нужно переместить эту деталь и поддерживать одинаковый угол между ними, чтобы она выглядела реалистично.

У меня есть этот код (при условии, что ввод в порядке, a.k.a радиус движущейся части одинаков.)

public void MoveParts(double newX, double newY)
{
    foreach (var part in InnerParts)
    {
        var angle = GetAngle(part);
        var radius = GetDistace(part.baseX,part.baseY,part.x,part.y);
        part.baseX = newX;
        part.baseY = newY;
        var t = Math.Atan2(this.y - this.baseY, this.x - this.baseX); //curr angle
        angle = 2 * Math.PI - angle + t; //proved via geometry
        part.x = part.baseX + Math.Cos(angle) * radius;
        part.y = part.baseY + Math.Sin(angle) * radius;
    }
    this.x = newX;
    this.y = newY;
}

private double GetAngle(Body part)
{
    return Math.Atan2(part.y - part.baseY, part.x - part.baseX) - Math.Atan2(this.baseY - this.y, this.baseX - this.x);
}
private double GetDistace(double x1, double y1, double x2, double y2)
{
    return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
}

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

Как программист, я могу просто пропустить это, выполняя два обновления в каждом кадре, но как фанатик, я хочу знать, где я ошибся.

Кто-нибудь, кто имеет хорошие знания геометрии, вы можете мне помочь?

Спасибо, Марк. Изображение (считаются кадры):

image

Ответы [ 3 ]

1 голос
/ 04 сентября 2011

Я знаю, что это строго не ответ на вопрос ОП, но, возможно, он может оказаться интересным в любом случае.

Итак, я прочитал ваш код и попытался выяснить, в чем проблема, но я начал думать: «Интересно, действительно ли OP нужен SceneGraph ».

Сейчас я не эксперт по SceneGraphs , но я подумал, что мог бы продемонстрировать, возможно, другой подход к созданию составных визуальных объектов.

Путем структурирования объектов таким способом проще создавать и поддерживать сложные структуры при вращении и перемещении.

Начните с определения абстрактного класса Shape

abstract class Shape
{
    static readonly Shape[] s_emptyChildren = new Shape[0];

    public Rect BoundingBox;
    public Transform Transform;
    public Shape[] Children = s_emptyChildren;

    public void RenderShape (DrawingContext context)
    {
        context.PushTransform (Transform ?? Transform.Identity);

        try
        {
            OnRenderShape (context);

            foreach (var shape in Children ?? s_emptyChildren)
            {
                shape.RenderShape (context);
            }
        }
        finally
        {
            context.Pop ();
        }
    }

    protected abstract void OnRenderShape (DrawingContext context);
}
  • Shape.BoundingBox - это прямоугольник, который определяет границы формы
  • Shape.Children - это массив всех внутренних фигур (в вашем примере я думаю, что вы называете его InnerParts
  • Shape.Transform - это преобразование, которое указывает, как этот Shape (и его дочерние элементы) относятся к своему родителю
  • Разработчики OnRenderShape реализуют этот метод для визуализации фигуры

Класс Transform может выражать простые и сложные преобразования, такие как:

// Translates (Moves) Shape 100 "pixels" to right in relation to its parent
var translation = new TranslateTransform (100, 0);
// Rotates Shape 30 degrees clockwise in relation to its parent
var rotation = new RotateTransform (30);
// Composite first translates then rotates the Shape
var composite = 
            new TransformGroup
            {
                Children =
                    new TransformCollection
                            {
                                translation,
                                rotation ,
                            },
            }

Итак, чтобы выразить простой составной объект, мы можем сделать это так:

static RectangleShape MakeSimpleShape()
{
    return
        new RectangleShape
            {
                BoundingBox = new Rect (-200, -200, 400, 400),
                Pen = s_redPen,
                Brush = null,
                Children =
                    new Shape[]
                        {
                            new RectangleShape
                                {
                                    BoundingBox = new Rect (-40, -40, 40, 40),
                                    Transform = new TranslateTransform (100, 100),
                                },
                        },
            };
}

Я сделал полный пример с рендерингом (с использованием WPF) на случай, если вы заинтересованы (MakeComplexShape в основном строит рекурсивную форму до определенного уровня)

  • Создание нового консольного приложения в Visual Studio
  • Добавить ссылку на WindowsBase, PresentationCore, PresentationFramework, System.Xaml
  • Вставить код в Program.cs

Должно быть хорошо, чтобы идти.

using System;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;

abstract class Shape
{
    static readonly Shape[] s_emptyChildren = new Shape[0];

    public Rect BoundingBox;
    public Transform Transform;
    public Shape[] Children = s_emptyChildren;

    public void RenderShape (DrawingContext context)
    {
        context.PushTransform (Transform ?? Transform.Identity);

        try
        {
            OnRenderShape (context);

            foreach (var shape in Children ?? s_emptyChildren)
            {
                shape.RenderShape (context);
            }
        }
        finally
        {
            context.Pop ();
        }
    }

    protected abstract void OnRenderShape (DrawingContext context);
}

sealed class RectangleShape : Shape
{
    static readonly SolidColorBrush s_defaultBrush = new SolidColorBrush (Colors.Green).FreezeIfNecessary ();

    public Pen Pen;
    public Brush Brush = s_defaultBrush;

    protected override void OnRenderShape (DrawingContext context)
    {
        context.DrawRectangle (Brush, Pen, BoundingBox);
    }
}

static class Extensions
{
    public static Color SetAlpha (this Color value, byte alpha)
    {
        return Color.FromArgb (alpha, value.R, value.G, value.B);
    }

    public static TValue FreezeIfNecessary<TValue>(this TValue value)
        where TValue : Freezable
    {
        if (value != null && value.CanFreeze)
        {
            value.Freeze ();
        }

        return value;
    }
}

class RenderShapeControl : FrameworkElement
{
    public Shape Shape;

    public Transform ShapeTransform;

    protected override void OnRender (DrawingContext drawingContext)
    {
        if (Shape != null)
        {
            try
            {
                drawingContext.PushTransform (new TranslateTransform (ActualWidth / 2, ActualHeight / 2).FreezeIfNecessary ());
                drawingContext.PushTransform (ShapeTransform ?? Transform.Identity);
                Shape.RenderShape (drawingContext);
            }
            finally
            {
                drawingContext.Pop ();
                drawingContext.Pop ();
            }
        }
    }

}

public class MainWindow : Window
{
    static readonly int[] s_childCount = new[] { 0, 5, 5, 5, 5, 5 };
    static readonly Brush s_redBrush = new SolidColorBrush (Colors.Red.SetAlpha (0x80)).FreezeIfNecessary ();
    static readonly Brush s_blueBrush = new SolidColorBrush (Colors.Blue.SetAlpha (0x80)).FreezeIfNecessary ();
    static readonly Pen s_redPen = new Pen (Brushes.Red, 2).FreezeIfNecessary ();
    static readonly Pen s_bluePen = new Pen (Brushes.Blue, 2).FreezeIfNecessary ();

    static Shape MakeInnerPart (int level, int index, int count, double outerside, double angle)
    {
        var innerSide = outerside / 3;
        return new RectangleShape
        {
            BoundingBox = new Rect (-innerSide / 2, -innerSide / 2, innerSide, innerSide),
            Pen = index == 0 ? s_bluePen : s_redPen,
            Brush = index == 0 && level > 0 ? s_redBrush : s_blueBrush,
            Children = MakeInnerParts (level - 1, innerSide),
            Transform =
                new TransformGroup
                {
                    Children =
                        new TransformCollection
                            {
                                new TranslateTransform (outerside/2, 0),
                                new RotateTransform (angle),
                            },
                }.FreezeIfNecessary (),
        };
    }

    static Shape[] MakeInnerParts (int level, double outerside)
    {
        var count = s_childCount[level];
        return Enumerable
            .Range (0, count)
            .Select (i => MakeInnerPart (level, i, count, outerside, (360.0 * i) / count))
            .ToArray ();
    }

    static RectangleShape MakeComplexShape ()
    {
        return new RectangleShape
        {
            BoundingBox = new Rect (-200, -200, 400, 400),
            Pen = s_redPen,
            Brush = null,
            Children = MakeInnerParts (3, 400),
        };
    }

    static RectangleShape MakeSimpleShape ()
    {
        return
            new RectangleShape
                {
                    BoundingBox = new Rect (-200, -200, 400, 400),
                    Pen = s_redPen,
                    Brush = null,
                    Children =
                        new Shape[]
                            {
                                new RectangleShape
                                    {
                                        BoundingBox = new Rect (-40, -40, 40, 40),
                                        Transform = new TranslateTransform (100, 100),
                                    },
                            },
                };
    }

    readonly DispatcherTimer m_dispatcher;
    readonly DateTime m_start = DateTime.Now;
    readonly RenderShapeControl m_shapeRenderer = new RenderShapeControl ();

    public MainWindow ()
    {
        AddChild (m_shapeRenderer);

        m_dispatcher = new DispatcherTimer (
            TimeSpan.FromSeconds (1 / 60),
            DispatcherPriority.ApplicationIdle,
            OnTimer,
            Dispatcher
            );
        m_dispatcher.Start ();
        m_shapeRenderer.Shape = MakeComplexShape ();
        //m_shapeRenderer.Shape = MakeSimpleShape ();
    }

    void OnTimer (object sender, EventArgs e)
    {
        var diff = DateTime.Now - m_start;
        var phase = (20 * diff.TotalSeconds) % 360.0;

        m_shapeRenderer.ShapeTransform =
            new TransformGroup
            {
                Children =
                    new TransformCollection
                        {
                            new TranslateTransform (100, 0),
                            new RotateTransform (phase),

                        },
            }.FreezeIfNecessary ();
        m_shapeRenderer.InvalidateVisual ();
    }
}

class Program
{
    [STAThread]
    static void Main (string[] args)
    {
        var mainWindow = new MainWindow ();
        mainWindow.ShowDialog ();
    }
}
1 голос
/ 04 сентября 2011

Хотя это не решение вашей проблемы, я бы изменил одну вещь:

    private double GetDistace(double x1, double y1, double x2, double y2)
    {
        return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
    }

на это:

    private double GetDistace(double x1, double y1, double x2, double y2)
    {
        double xd = x1 - x2;
        doubly yd = y1 - y2;
        return Math.Sqrt(xd * xd + yd * yd);
    }

Это должно быть как быстрее, так и(возможно) более точный, чем Math.Pow.

0 голосов
/ 06 сентября 2011

Самый большой "беспорядок" - это способ определения angle.Вы пишете это (надеюсь, вы понимаете мой синтаксис)

angle = angleof(part.XY relative part.baseXY) - angleof(this.baseXY relative this.XY)
angle = 2*pi - angle + angleof(this.XY relative this.baseXY)

Новая координата не влияет на угол ... Если вы хотите вращение вокруг this.baseXY, я бы попробовал:

angle = angleof(part.XY relative part.baseXY) + angleof(this.XY relative this.baseXY) - angleof(new.XY relative this.baseXY)
part.baseXY = new.XY
part.XY = part.baseXY + radius * cos/sin(angle)

Хорошо бы проверить, что вызов MoveParts с теми же координатами, что и у текущего (this.MoveParts(this.X, this.Y), тогда ничего не должно произойти ...

(кстати, добавление 2 * pi к углу не имеет значения)при расчете cos и sin)

И ... если вы планируете делать более сложные вещи, тогда посмотрите на другой способ обработки ваших координат, но это другой вопрос ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...