W tym poście y = f (t), gdzie t jest zmiennym parametrem (czas / postęp), ay jest odległością do celu. Będę mówić o punktach na wykresach 2D, w których oś pozioma to czas / postęp, a pion to odległość.
Myślę, że możesz zrobić sześcienną krzywą Beziera z pierwszym punktem w (0, 1) i czwartym (ostatnim) punktem w (1, 0). Dwa środkowe punkty mogą być losowo umieszczone (x = rand, y = rand) w tym prostokącie 1 na 1. Nie jestem w stanie zweryfikować tego analitycznie, ale po prostu bawię się apletem (tak, śmiej się), wydaje się, że krzywa Beziera nigdy nie zmniejszy się z takim ograniczeniem.
Będzie to twoja elementarna funkcja b (p1, p2), która zapewnia nie malejącą ścieżkę od punktu p1 do punktu p2.
Teraz możesz wygenerować ab (p (1) = (0, 1), p (n) = (1, 0)) i wybrać liczbę p (i) wzdłuż tej krzywej, tak aby 1
Zasadniczo generujesz jedną „ogólną” ścieżkę, a następnie dzielisz ją na segmenty i regenerujesz każdy segment.
Ponieważ potrzebujesz funkcji matematycznej: Załóżmy, że powyższa procedura jest spakowana w jedną funkcję y = f (t, s), która daje odległość w t dla funkcji nasion s. Będziesz potrzebować:
- 4 losowe liczby do umieszczenia 2 środkowych punktów głównego splajnu Beziera (od (0, 1) do (1, 0))
- liczby n-1 dla granic każdego segmentu, jeśli masz n segmentów (pierwszy segment zawsze zaczyna się od (0, 1) tj. t = 0, a ostatni kończy się na (1,0) tj. t = 1)
- 1 liczba, jeśli chcesz losowo wybrać liczbę segmentów
- 4 dodatkowe liczby do umieszczenia środkowych punktów splajnu odcinka, na który wyląduje t
Dlatego każde ziarno musi dostarczyć jedno z poniższych:
- 7 + n liczb rzeczywistych od 0 do 1 (jeśli chcesz kontrolować liczbę segmentów)
- 7 liczb rzeczywistych i jedna liczba całkowita większa niż 1 (dla losowej liczby segmentów)
Wyobrażam sobie, że możesz to osiągnąć, podając po prostu tablicę liczb jako nasion. Alternatywnie, możesz zrobić coś takiego jak podać jedną liczbę s jako ziarno, a następnie wywołać wbudowany generator liczb losowych za pomocą rand (s), rand (s + 1), rand (s + 2) i tak dalej (lub zainicjować za pomocą s, a następnie nadal dzwonić rand.NextNumber).
Zauważ, że chociaż cała funkcja f (t, s) składa się z wielu segmentów, oceniasz tylko jeden segment dla każdego t. Państwo będzie musiał wielokrotnie obliczać granice segmentów z tej metody, ponieważ trzeba będzie uporządkować je, aby upewnić się nie ma dwóch segmentów zachodzą na siebie. Prawdopodobnie możesz zoptymalizować i pozbyć się tej dodatkowej pracy i znaleźć tylko punkty końcowe jednego segmentu dla każdego połączenia, ale nie jest to dla mnie teraz oczywiste.
Ponadto krzywe Beziera nie są konieczne, wystarczy odpowiednio zachowujący splajn.
Stworzyłem przykładową implementację Matlaba.
Funkcja Beziera (wektoryzowana):
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
Opisana powyżej złożona funkcja Beziera (celowo pozostawiona bez wektoryzacji, aby wyjaśnić, ile oceny jest potrzebne dla każdego połączenia):
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
Skrypt, który wykreśla funkcję losowego ziarna (zauważ, że jest to jedyne miejsce, w którym wywoływana jest funkcja losowa, zmienne losowe do wszystkich innych kodów są propagowane z tej jednej losowej tablicy):
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');
Oto przykładowy wynik:
Wygląda na to, że spełnia większość twoich kryteriów. Jednak:
- Są „rogi”. Można to poprawić, stosując bardziej odpowiednie krzywe Beziera.
- „Oczywiście” wygląda jak splajny, chociaż tak naprawdę nie możesz zgadnąć, co zrobi po nietrywialnym czasie, chyba że znasz ziarno.
- Bardzo rzadko odchyla się zbytnio w stronę narożnika (można to naprawić, grając z rozkładem generatora nasion).
- Sześcienna funkcja Beziera nie może dotrzeć do obszaru w pobliżu rogu, biorąc pod uwagę te ograniczenia.
f'(x)>0
, więc znormalizowana integracja wartości bezwzględnej dowolnej funkcji hałasu spełni wszystkie Twoje wymagania. Niestety nie znam żadnego łatwego sposobu na obliczenie tego, ale może ktoś inny to zna. :)