Для этого поста y = f (t), где t - параметр, который вы меняете (время / прогресс), а y - расстояние до цели. Поэтому я буду говорить с точки зрения точек на двухмерных графиках, где горизонтальная ось - это время / прогресс, а вертикальная - это расстояние.
Я думаю, что вы можете сделать кубическую кривую Безье с первой точкой в (0, 1) и четвертой (последней) точкой в (1, 0). Две средние точки могут быть расположены случайным образом (x = rand, y = rand) в этом прямоугольнике 1 на 1. Я не могу проверить это аналитически, но, просто поиграв с апплетом (да, продолжайте смеяться), кажется, что кривая Безье никогда не уменьшится при таком ограничении.
Это будет ваша элементарная функция b (p1, p2), которая обеспечивает неубывающий путь от точки p1 к точке p2.
Теперь вы можете сгенерировать ab (p (1) = (0, 1), p (n) = (1, 0)) и выбрать число p (i) вдоль этой кривой так, чтобы 1
По сути, вы генерируете один «общий» путь, а затем разбиваете его на сегменты и восстанавливаете каждый сегмент.
Поскольку вам нужна математическая функция: предположим, что описанная выше процедура упакована в одну функцию y = f (t, s), которая дает вам расстояние в точке t для функции seed s. Тебе понадобится:
- 4 случайных числа для размещения 2 средних точек основного сплайна Безье (от (0, 1) до (1, 0))
- n-1 число для границ каждого сегмента, если у вас есть n сегментов (первый сегмент всегда начинается в (0, 1), т.е. t = 0, а последний заканчивается в (1,0), т.е. t = 1)
- 1 число, если вы хотите рандомизировать количество сегментов
- Еще 4 числа для размещения средних точек сплайна сегмента, в который вы попали
Таким образом, каждое семя должно обеспечивать одно из следующего:
- 7 + n действительных чисел от 0 до 1 (если вы хотите контролировать количество сегментов)
- 7 действительных чисел и одно целое число больше 1 (для случайного числа сегментов)
Я полагаю, что вы можете выполнить любой из них, просто указав в качестве начального числа массив чисел. В качестве альтернативы вы можете сделать что-то вроде предоставления одного числа s в качестве начального числа, а затем вызвать встроенный генератор случайных чисел с помощью rand (s), rand (s + 1), rand (s + 2) и т. Д. (Или инициализировать с помощью и затем продолжайте вызывать rand.NextNumber).
Обратите внимание, что, хотя вся функция f (t, s) состоит из множества сегментов, вы оцениваете только один сегмент для каждого t. Вам будет необходимо повторно вычислить границы сегментов с помощью этого метода, потому что вам придется сортировать их , чтобы убедиться , что никакие два сегмента перекрываются. Вероятно, вы можете оптимизировать и избавиться от этой дополнительной работы и найти только конечные точки одного сегмента для каждого вызова, но это не очевидно для меня сейчас.
Кроме того, кривые Безье не нужны, подойдет любой подходящий сплайн.
Я создал образец реализации Matlab.
Функция Безье (векторизация):
function p = bezier(t, points)
% p = bezier(t, points) takes 4 2-dimensional points defined by 2-by-4 matrix
% points and gives the value of the Bezier curve between these points at t.
%
% t can be a number or 1-by-n vector. p will be an n-by-2 matrix.
coeffs = [
(1-t').^3, ...
3*(1-t').^2.*t', ...
3*(1-t').*t'.^2, ...
t'.^3
];
p = coeffs * points;
end
Составная функция Безье, описанная выше (намеренно оставлена безвекторной, чтобы прояснить, сколько оценки требуется для каждого вызова):
function p = bezier_compound(t, ends, s)
% p = bezier(t, points) takes 2 2-dimensional endpoints defined by a 2-by-2
% matrix ends and gives the value of a "compound" Bezier curve between
% these points at t.
%
% t can be a number or 1-by-n vector. s must be a 1-by-7+m vector of random
% numbers from 0 to 1. p will be an n-by-2 matrix.
%% Generate a list of segment boundaries
seg_bounds = [0, sort(s(9:end)), 1];
%% Find which segment t falls on
seg = find(seg_bounds(1:end-1)<=t, 1, 'last');
%% Find the points that segment boundaries evaluate to
points(1, :) = ends(1, :);
points(2, :) = [s(1), s(2)];
points(3, :) = [s(3), s(4)];
points(4, :) = ends(2, :);
p1 = bezier(seg_bounds(seg), points);
p4 = bezier(seg_bounds(seg+1), points);
%% Random middle points
p2 = [s(5), s(6)] .* (p4-p1) + p1;
p3 = [s(7), s(8)] .* (p4-p1) + p1;
%% Gather together these points
p_seg = [p1; p2; p3; p4];
%% Find what part of this segment t falls on
t_seg = (t-seg_bounds(seg))/(seg_bounds(seg+1)-seg_bounds(seg));
%% Evaluate
p = bezier(t_seg, p_seg);
end
Скрипт, который строит функцию для случайного начального числа (обратите внимание, что это единственное место, где вызывается случайная функция, случайные переменные для всего остального кода распространяются из этого одного случайного массива):
clear
clc
% How many samples of the function to plot (higher = higher resolution)
points = 1000;
ends = [
0, 0;
1, 1;
];
% a row vector of 12 random points
r = rand(1, 12);
p = zeros(points, 2);
for i=0:points-1
t = i/points;
p(i+1, :) = bezier_compound(t, ends, r);
end
% We take a 1-p to invert along y-axis here because it was easier to
% implement a function for slowly moving away from a point towards another.
scatter(p(:, 1), 1-p(:, 2), '.');
xlabel('Time');
ylabel('Distance to target');
Вот пример вывода:
Кажется, соответствует большинству ваших критериев. Однако:
- Есть "углы". Это может быть исправлено с помощью более подходящих кривых Безье.
- Это «очевидно» выглядит как сплайны, хотя вы не можете точно догадаться, что он будет делать после нетривиального периода времени, если вы не знаете семя.
- Он очень редко отклоняется слишком сильно к углу (это можно исправить, играя с распределением генератора семян).
- Кубическая функция Безье не может достичь области вблизи угла с учетом этих ограничений.
f'(x)>0
образом: нормализованная интеграция абсолютного значения любой шумовой функции будет соответствовать всем вашим требованиям. К сожалению, я не знаю ни одного простого способа рассчитать это, но, может быть, кто-то еще знает. :)