Пропорциональный перевод
Я хотел бы обновить список точек (PointFs), выполнив вращение (вокруг нового источника) и переместив каждую точку на величину, пропорциональную ее текущему расстоянию от начала координат (поэтому не абсолютный перевод).
В настоящее время я делаю это для каждой точки по очереди, но при движении больше, чем несколько точек, производительность низкая.
Я хотел бы сделать преобразование более эффективным, поэтому хотел использовать матрицу. Вращение не проблема, но я не знаю, как сделать пропорциональный перевод.
Могу ли я сделать это с помощью аффинной матрицы? Есть ли другой способ сделать преобразование более эффективно?
ОБНОВЛЕНО
Вот мой текущий код. Я немного изменил его, так что, по крайней мере, он использует матрицу для вращения. Обратите внимание, что перевод основан на соотношении, поэтому точки ближе к центру не будут перемещаться так далеко, как точки дальше:
private void DragPointsAroundCentre(PointF centre, PointF priorLocation, PointF newLocation, PointF[] otherPoints)
{
// calculate the angle and length of the transformation from the original location
var priorLength = Maths.Distance(centre, priorLocation);
var newLength = Maths.Distance(centre, newLocation);
var lengthRatio = newLength / priorLength;
var rotationAngle = (float)Maths.Angle(centre, priorLocation, newLocation);
// apply the rotation to the other points
Rotate(otherPoints, rotationAngle, centre);
// apply an equivalent translation to the other points
for (int i = 0; i < otherPoints.Length ; i++)
{
var translation = GetPointOnLine(centre, otherPoints[i], (float) lengthRatio);
otherPoints[i].X = translation.X;
otherPoints[i].Y = translation.Y;
}
}
private static void Rotate(PointF[] points, float angle, PointF center)
{
using (Matrix m = new Matrix())
{
m.RotateAt(angle, center);
m.TransformPoints(points);
}
}
// gets a point from a relative position on a line using the specified ratio
private static PointF GetPointOnLine(PointF origin, PointF point, float ratio)
{
return new PointF(
origin.X + (point.X - origin.X) * ratio,
origin.Y + (point.Y - origin.Y) * ratio);
}
1 ответ
Это код, который я использую для преобразований. Я надеюсь, это поможет вам:
class Program
{
static void Main(string[] args)
{
PointF[] points = new PointF[]
{
new PointF(1, 0),
new PointF(0, 1)
};
float angle = 90; // in degrees
PointF center = new PointF(1, 1);
Rotate(points, angle, center);
float offset = 10;
PointF vector = new PointF(1, 1);
Translate(points, offset, vector);
}
static void Rotate(PointF[] points, float angle, PointF center)
{
using (Matrix m = new Matrix())
{
m.RotateAt(angle, center);
m.TransformPoints(points);
}
}
// Translates point along the specified vector.
static void Translate(PointF[] points, float offset, PointF vector)
{
float magnitude = (float)Math.Sqrt((vector.X * vector.X) + (vector.Y * vector.Y)); // = length
vector.X /= magnitude;
vector.Y /= magnitude;
PointF translation = new PointF()
{
X = offset * vector.X,
Y = offset * vector.Y
};
using (Matrix m = new Matrix())
{
m.Translate(translation.X, translation.Y);
m.TransformPoints(points);
}
}
}
Если вам нужно, чтобы преобразование было очень эффективным, вы можете объединить обе матрицы преобразования в одну и преобразовать все точки только один раз.
РЕДАКТИРОВАТЬ:
Например, вы можете использовать простой параллельный цикл, чтобы сделать его немного быстрее. Но даже для 30 000 000 баллов разница не слишком велика (в моем случае 4 ядра). Но, конечно, это зависит от того, как часто вы их обрабатываете.
class Program
{
static void Main(string[] args)
{
int pointCount = 30000000;
PointF[] otherPoints = new PointF[pointCount];
Random rnd = new Random();
for (int i = 0; i < pointCount; i++)
{
otherPoints[i] = new Point(rnd.Next(), rnd.Next());
}
PointF centre = new PointF(3, 3);
float lengthRatio = 7.3f;
// apply an equivalent translation to the other points
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < otherPoints.Length; i++)
{
var translation = GetPointOnLine(centre, otherPoints[i], (float)lengthRatio);
otherPoints[i].X = translation.X;
otherPoints[i].Y = translation.Y;
}
sw.Stop();
Console.WriteLine("Single thread: {0} sec.", sw.Elapsed.TotalSeconds);
sw.Reset();
sw.Start();
Parallel.For(0, pointCount, i =>
{
var translation = GetPointOnLine(centre, otherPoints[i], (float)lengthRatio);
otherPoints[i].X = translation.X;
otherPoints[i].Y = translation.Y;
});
sw.Stop();
Console.WriteLine("Multi thread: {0} sec.", sw.Elapsed.TotalSeconds);
Console.ReadKey();
}
// gets a point from a relative position on a line using the specified ratio
private static PointF GetPointOnLine(PointF origin, PointF point, float ratio)
{
return new PointF(
origin.X + (point.X - origin.X) * ratio,
origin.Y + (point.Y - origin.Y) * ratio);
}
}
РЕДАКТИРОВАТЬ-2:
Я нашел преобразование, которое точно такое же, как ваше, и преобразовывает точки только в одном цикле, используя одну матрицу. Вот код для старого и нового преобразования:
class Program
{
static void Main(string[] args)
{
PointF[] points1 = new PointF[]
{
new PointF(1f, 0f),
new PointF(0f, 1f),
new PointF(1f, 1f),
new PointF(2f, 2f),
};
PointF[] points2 = new PointF[]
{
new PointF(1f, 0f),
new PointF(0f, 1f),
new PointF(1f, 1f),
new PointF(2f, 2f),
};
PointF center = new PointF(2f, 2f);
float priorLength = 4f;
float newLength = 5f;
float lengthRatio = newLength / priorLength;
float rotationAngle = 45f;
Transformation_old(points1, rotationAngle, center, lengthRatio);
Transformation_new(points2, rotationAngle, center, lengthRatio);
Console.ReadKey();
}
static void Transformation_old(PointF[] points, float rotationAngle, PointF center, float lengthRatio)
{
Rotate(points, rotationAngle, center);
for (int i = 0; i < points.Length; i++)
{
var translation = GetPointOnLine(center, points[i], lengthRatio);
points[i].X = translation.X;
points[i].Y = translation.Y;
}
}
static void Rotate(PointF[] points, float angle, PointF center)
{
using (Matrix m = new Matrix())
{
m.RotateAt(angle, center);
m.TransformPoints(points);
}
}
private static PointF GetPointOnLine(PointF origin, PointF point, float ratio)
{
return new PointF(
origin.X + (point.X - origin.X) * ratio,
origin.Y + (point.Y - origin.Y) * ratio);
}
// Uses only a single matrix and a single transformation:
static void Transformation_new(PointF[] points, float rotationAngle, PointF center, float lengthRatio)
{
using (Matrix m = new Matrix())
{
m.RotateAt(rotationAngle, center, MatrixOrder.Prepend);
// Replaces GetPointOnLine
m.Translate(center.X, center.Y, MatrixOrder.Prepend);
m.Scale(lengthRatio, lengthRatio, MatrixOrder.Prepend);
m.Translate(-center.X, -center.Y, MatrixOrder.Prepend);
m.TransformPoints(points);
}
}
}