На данной странице описан пример вычисления неизвестных координат вектора по известным. Процесс нахождения координат построен на простой арифметике, без использования углов, синусов и другой тригонометрии.
Теоретические раскладки подкреплены практическим исходным кодом на языке C#. К странице прикреплен исходник приложения демонстрирующее анимацию движения эллипсов по вычисленным векторам.
При масштабировании вектора может быть неизвестна (или сложно определяемая) величина изменения размера, но известна одна из координат конечной точки и координаты исходного вектора. Этих данных достаточно для определения искомой координаты конечной точки вектора.
Как вычислить неизвестные координаты? Величины изменения координаты X на одну единицу координаты Y исходного и масштабируемого векторов всегда равны. Такое тождество справедливо при увеличении и уменьшении длины вектора. Используя данное соотношение можно найти любую неизвестную координату вектора. Для вывода формулы вычисления необходимо определить векторы.
Под определением вектора, или координат вектора, подразумевается перенос вектора в начало координат. Определенный вектор имеет начальную точку с координатами (0; 0). Координаты вектора определяются как разность координат точек его конца и начала.
Формула соотношения изменения координаты X на одну единицу координаты Y исходного и масштабируемого векторов с одновременным определением
x1 – x x2 – x
-------- = --------- (у.1)
y1 - y y2 – y
или уже определенных векторов
x1 x2
--- = ---
y1 y2
Примеры вычисления координаты X конечных точек векторов разных направлений. Даны два исходных вектора AB и MN, которые имеют предполагаемые конечные точки соответственно C(x2 ; 2) и P(x2 ; 4). Требуется найти координаты x2 конечных точек масштабируемых векторов AC и MP.
Используя уравнение 1 (см. выше) вычислим неизвестные координаты вектора AC.
Определение исходного вектора:
AB(x1 - x; y1 - y) => AB(4.5 - 1.5; 1.381 - 1) => AB(3; 0.381)
Определение масштабируемого вектора:
AC(x2 - x; y2 - y) => AC(x2 - 1.5; 2 - 1) => AC(x2 - 1.5; 1)
одно уравнение (у.1) и одно неизвестное - система решаема.
Подставляем координаты определенных векторов в у.1
3 x2 - 1.5 3 * 1
-------- = --------- => x2 = --------- + 1.5 => x2 = 9.374
0.381 1 0.381
Координаты точки C (9.374; 2) и вектора AC(9.374 - 1.5; 2 - 1). Задача решена.
Используя уравнение 1 (см. выше) аналогично вычислим неизвестные координаты вектора MP.
Определение исходного вектора:
MN(x1 - x; y1 - y) => MN(7 - 11; 5.18 - 6) => MN(-4; -0.82)
Определение масштабируемого вектора:
MP(x2 - x; y2 - y) => MP(x2 - 11; 4 - 6) => MP(x2 - 11; -2)
одно уравнение (у.1) и одно неизвестное - система решаема.
Подставляем координаты определенных векторов в у.1
-4 x2 - 11 -4 * -2
-------- = --------- => x2 = --------- + 11 => x2 = 1.2439
-0.82 -2 -0.82
Координаты точки P (1.2439; 4) и вектора MP(1.2439 - 11; 4 - 6). Задача решена.
По формуле у.1 (см. выше) отношений координат X к Y (или наоборот) формирующих точек исходных и искомых векторов, можно вычислить любую неизвестную координату. Теперь вычислим неизвестную Y используя известную координату X. Причем в данном примере векторы AB и MN будут масштабироваться с уменьшением в длине, т. е. откатываться назад.
Определение исходного вектора:
AB(x1 - x; y1 - y) => AB(11 - 1; 3.181 - 1) => AB(10; 2.181)
Определение масштабируемого вектора:
AC(x2 - x; y2 - y) => AC(5 - 1; y2 - 1) => AC(4; y2 - 1)
одно уравнение (у.1) и одно неизвестное - система решаема.
Подставляем координаты определенных векторов в у.1
10 4 2.181 * 4
------- = --------- => y2 = --------- + 1 => y2 = 1.8724
2.181 y2 - 1 10
Координаты точки C (5; 1.8724) и вектора AC(5 - 1; 1.8724 - 1). Задача решена.
Вычисление неизвестных координат вектора MP.
Определение исходного вектора:
MN(x1 - x; y1 - y) => MN(3 - 8; 6.415 - 4) => MN(-5; 2.415)
Определение масштабируемого вектора:
MP(x2 - x; y2 - y) => MP(5.45 - 8; y2 - 4) => AC(-2.55; y2 - 4)
одно уравнение (у.1) и одно неизвестное - система решаема.
Подставляем координаты определенных векторов в у.1
-5 -2.55 2.415 * (-2.55)
------- = --------- => y2 = ---------------- + 4 => y2 = 5.23165
2.415 y2 - 4 -5
Координаты точки P (5.45; 5.23165) и вектора AC(5.45 - 8; 5.23165 - 4). Задача решена.
Практический пример на C# представляет собой анимацию движения эллипсов по траекториям вычисленных векторов. Программный код использует формулу у.1 (см. выше) для уменьшения и увеличения длины вектора. После вычисления искомых векторов запускается анимация перемещения эллипсов. Для большей зрелищности перед каждым перемещением эллипсы получают случайные размеры.
Программный код приложения демонстрации вычисления неизвестных координат вектора по известным. Создайте приложение WPF .NET в Visual Studio 2022 и скопируйте (замените) программный код в файл класса MainWindow.xaml.cs и разметку в файл MainWindow.xaml. Кроме этого, архив исходника данного приложения прикреплен внизу этой страницы.
Описание метода вычисления координат вектора подробно описан ниже.
Полный программный код приложения:
public partial class MainWindow : Window
{
// Селектор движения по исходному или вычисленным векторам.
// 0 - исходный вектор
// 1 - уменьшение длины (откат) вектора
// 2 - увеличение длины вектора
private int _typeTrajectory = 0;
private readonly Random _random = new();
// Фигуры учавствовающие в анимации движения по векторам
private readonly Shape[] shapes = new Shape[3];
// Счетчик движения фигур для синхронизации анимаций
private int order = 0;
public MainWindow()
{
InitializeComponent();
// Массив фигур для движения по траекториям.
shapes[0] = ellipse1;
shapes[1] = ellipse2;
shapes[2] = ellipse3;
}
private void Canvas1_SizeChanged(object sender, SizeChangedEventArgs e)
{
// Обозначение точки начала движения эллипсов.
Canvas.SetLeft(ellipseCenter, canvas1.ActualWidth / 2);
Canvas.SetTop(ellipseCenter, canvas1.ActualHeight / 2);
// Корректировка размеров родителей фигур
border1.Margin = new Thickness(0, ActualHeight / 6, 0, ActualHeight / 6);
canvas1.Margin = new Thickness(border1.ActualWidth / 6, 0, border1.ActualWidth / 6, 0);
// --- Инициализация координат эллипсов ---
for (int i = 0; i < shapes.Length; i++)
{
Canvas.SetLeft(shapes[i], canvas1.ActualWidth / 2);
Canvas.SetTop(shapes[i], canvas1.ActualHeight / 2);
}
}
///
/// Вычисление неизвестной координаты
///
private static (double x2, double y2) ComputeUnknowCoordinates(Shape shape, int trajectory, double startX, double startY, double toX, double toY)
{
// Данные для вычисления координаты X искомого вектора.
double x = startX;
double y = startY;
double x1 = toX;
double y1 = toY;
// Неизвестная координата
double x2;
// Выбор направления масштабирования искомого вектора
int k = 1;
// 1 - уменьшение (откат) вектора
// -1 - удлинение вектора
if (trajectory == 2) k = -1;
double y2 = toY - k * (shape.Width / 2) * Math.Sign(toY - startY);
// Вычисление неизвестной координаты.
x2 = (x1 - x) * (y2 - y) / (y1 - y) + x;
return (x2, y2);
}
#region Управление анимациями движения по траекториям векторов
///
/// Запуск анимаций
///
private void Button_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
switch (button.Content.ToString())
{
case "Запуск":
StartAnimations();
button.IsEnabled = false;
break;
case "Изменение конечной точки":
_ = _typeTrajectory == 2 ? _typeTrajectory = 0 : _typeTrajectory++;
if (_typeTrajectory == 0) Title = "Формальная траектория - исходный вектор направления";
if (_typeTrajectory == 1) Title = "Движение до препятствия - уменьшение вектора (откат)";
if (_typeTrajectory == 2) Title = "Скрытие элемента за горизонт - удлинение вектора";
break;
}
}
}
///
/// Запуск анимации через определенное время.
///
private void DelayAnimation()
{
DispatcherTimer timer = new() { Interval = TimeSpan.FromSeconds(1.5) };
timer.Tick += (sender, args) =>
{
timer.Stop();
StartAnimations();
};
timer.Start();
}
#endregion
#region Методы анимаций
///
/// Запуск одновременных анимаций
///
private void StartAnimations()
{
for (int i = 0; i < shapes.Length; i++)
{
StartMotion(shapes[i]);
}
}
///
/// Запуск движения отдельной фигуры
///
/// текущий эллипс
private void StartMotion(Shape shape)
{
// Начало движения от центра родителя.
double startX = canvas1.ActualWidth / 2;
double startY = canvas1.ActualHeight / 2;
// Временные линия для обозначения вектора траектории.
(Line line1, Line line2) = VectorDirectionLines(false, _typeTrajectory);
// Задержка перед началом перемещения
double beginTime = 0.5;
// Случайный выбор конечных координат.
double toX = _random.Next(0, (int)canvas1.ActualWidth);
double toY = canvas1.ActualHeight;
// Конечная точка может быть вверху.
int r = _random.Next(0, 2);
if (r % 2 == 0) toY = 0;
// Чертим линию
line1.X1 = startX;
line1.Y1 = startY;
line1.X2 = toX;
line1.Y2 = toY;
// Линия завершения траектории движения.
line2.X1 = toX;
line2.Y1 = toY;
// Если включена коррекция завершения траектории.
if (_typeTrajectory > 0)
{
(toX, toY) = ComputeUnknowCoordinates(shape, _typeTrajectory, startX, startY, toX, toY);
}
// Конечные точки линии коррекции траектории
line2.X2 = toX;
line2.Y2 = toY;
// Продолжительность анимации движения
double duration = 0.4;
//
DoubleAnimation xMotion = new()
{
To = toX,
FillBehavior = FillBehavior.Stop,
Duration = new Duration(TimeSpan.FromSeconds(duration)),
BeginTime = TimeSpan.FromSeconds(beginTime)
};
DoubleAnimation yMotion = new()
{
To = toY,
FillBehavior = xMotion.FillBehavior,
Duration = xMotion.Duration,
BeginTime = xMotion.BeginTime,
};
// Задержка в конечной точки для удобства визуализации.
duration = 0.8;
//
DoubleAnimation xStop = new()
{
From = toX,
To = toX,
FillBehavior = FillBehavior.Stop,
Duration = new Duration(TimeSpan.FromSeconds(duration))
};
DoubleAnimation yStop = new()
{
From = toY,
To = toY,
FillBehavior = xStop.FillBehavior,
Duration = xStop.Duration,
};
xMotion.Completed += (sender, eArgs) =>
{
shape.BeginAnimation(Canvas.LeftProperty, xStop);
shape.BeginAnimation(Canvas.TopProperty, yStop);
};
xStop.Completed += (sender, eArgs) =>
{
// Удаление временных линий
VectorDirectionLines(true);
// Случайное изменение размера текущей фигуры перед анимацией.
shape.Width = shape.Height = _random.Next(8, (int)border1.Margin.Top * 2);
// Позиционирование элемента будет происходить относительно его центра.
shape.Margin = new Thickness(shape.Width / 2 * -1, shape.Width / 2 * -1, 0, 0);
// Возврат фигуры на исходную позицию - в центр родителя.
Canvas.SetLeft(shape, canvas1.ActualWidth / 2);
Canvas.SetTop(shape, canvas1.ActualHeight / 2);
// Синхронизация одновременного запуска нескольких элементов.
order++;
if (order == shapes.Length)
{
order = 0;
// Запуск отложеной анимации.
DelayAnimation();
}
};
// Запуск анимации движения.
shape.BeginAnimation(Canvas.LeftProperty, xMotion);
shape.BeginAnimation(Canvas.TopProperty, yMotion);
}
///
/// Визуализация направления векторов при помощи линий.
///
private (Line line1, Line line2) VectorDirectionLines(bool erase, int trajectory = 0)
{
// Временная линия для обозначения траектории
Line line1 = new()
{
StrokeThickness = 1,
Stroke = Brushes.DarkBlue,
StrokeDashArray = new DoubleCollection(new double[] { 2, 2 }),
Uid = "lineTemp",
};
Panel.SetZIndex(line1, -2);
// Временная линия уменьшения или удлинения вектора
Line line2 = new()
{
StrokeThickness = 3,
Stroke = trajectory == 2 ? Brushes.DarkBlue : Brushes.Red,
Uid = "lineTemp",
};
Panel.SetZIndex(line2, -1);
if (erase == true)
{
// Удаляем помеченные элементы-линии
foreach (UIElement e in canvas1.Children.OfType().ToList())
{
if (e.Uid.Equals("lineTemp") == true) canvas1.Children.Remove(e);
}
}
else
{
// Добавляем линии в коллекцию родителя.
canvas1.Children.Add(line1);
canvas1.Children.Add(line2);
}
return (line1, line2);
}
#endregion
}
Программный код метода вычисления небольшой, но он обеспечивает четкую визуализацию движения фигур по исходным и вычисленным векторам. В методе реализован пример вычисления неизвестной координаты X по известной Y, значение которой Y = 0 или Y = Parent.Height. Код для вычисления координаты Y строится по такой же формуле у.1 (см. выше), только перемещение элементов в этом случае будет по известной координате X = 0 или X = Parent.Width.
Значение свойства shape.Width определяет диаметр эллипса. Элементы принудительно позиционируются относительно своего центра с помощью свойства Shape.Margin, это позволяет указывать точные координаты перемещения визуальных элементов. По умолчанию позиционирование эллипсов предусмотрено по левому верхнему углу.
Выражение toY – k * (shape.Width / 2) определяет направление масштабирования вектора:
- при k = 1 вектор перемещения уменьшается в длине;
- при k = -1 вектор перемещения увеличивается в длине;
Так как позиция элемента отсчитывается от его центра, значение выражения shape.Width / 2 вычитается из известной координаты искомого вектора, в данном случае из координаты Y. В зависимости от коэффициента k эллипс останавливается о препятствие либо скрывается за «горизонтом».
Выражение Math.Sign(toY - startY) так же влияет на направление масштабирования и выводит необходимый знак в зависимости от конечной координаты:
- если перемещение от центра до Y = Parent.Height знак положительный,
- при перемещении от центра до Y = 0 знак отрицательный.
Исходный код метода вычисления неизвестной координаты вектора:
// Вычисление неизвестной координаты
private static (double x2, double y2) ComputeUnknowCoordinates(
Shape shape, int trajectory, double startX, double startY, double toX, double toY)
{
// Данные для вычисления координаты X искомого вектора.
double x = startX;
double y = startY;
double x1 = toX;
double y1 = toY;
// Неизвестная координата
double x2;
// Выбор направления масштабирования искомого вектора
int k = 1;
// 1 - уменьшение (откат) вектора
// -1 - удлинение вектора
if (trajectory == 2) k = -1;
double y2 = toY – k * (shape.Width / 2) * Math.Sign(toY - startY);
// Вычисление неизвестной координаты.
x2 = (x1 - x) * (y2 - y) / (y1 - y) + x;
return (x2, y2);
}