C#的三角函数(或准确度错误?)问题

时间:2011-09-04 17:04:06

标签: c# math geometry

我正在尝试创建一个可以制作动画的程序 - 通过在屏幕上绘制一个简笔画,并允许用户移动其手臂和腿等等。

但是如果我想将两端连接的部分(或图形)移动到另一部分,我也需要移动那部分,并保持它们之间的相同角度,所以看起来很逼真。

我有这个代码(假设输入正常,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 个答案:

答案 0 :(得分:1)

虽然这不是解决问题的方法,但我要改变的一点是:

    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更快和(可能)更准确。

答案 1 :(得分:1)

我知道这绝对不是OP问题的答案,但无论如何它可能都很有趣。

所以我读了你的代码并试图找出问题所在,但我开始思考:“我想知道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 bounds
  • 的Rectangle
  • 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 ();
    }
}

答案 2 :(得分:0)

最大的“混乱”是确定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),那么什么都不应该发生......

(在计算cossin时,向一个角度添加2 * pi没有任何区别

而且......如果你打算做更高级的事情,那么请研究另一种处理坐标的方法,但那是另一个问题......