我正在尝试创建一个可以制作动画的程序 - 通过在屏幕上绘制一个简笔画,并允许用户移动其手臂和腿等等。
但是如果我想将两端连接的部分(或图形)移动到另一部分,我也需要移动那部分,并保持它们之间的相同角度,所以看起来很逼真。
我有这个代码(假设输入正常,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));
}
问题是,在每次更新时,移动的部分(不是基础部分)具有其原始值(在基础部分之间) - 或者是不同的值,每帧都在变化 - 尽管在两帧中它一次似乎工作正常。
作为程序员,我可以通过每帧进行两次更新来跳过它,但作为一个极客,我想知道我哪里出错。
任何具有良好几何知识的人,你能帮助我吗?
谢谢,马克。 图像(帧数):
答案 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);
}
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基本上构建一个递归形状达到一定水平)
应该不错。
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)
,那么什么都不应该发生......
(在计算cos
和sin
时,向一个角度添加2 * pi没有任何区别
而且......如果你打算做更高级的事情,那么请研究另一种处理坐标的方法,但那是另一个问题......