Para este post, y = f (t) onde t é o parâmetro que você varia (tempo / progresso) e y é a distância do alvo. Então, falarei em termos de pontos em gráficos 2D em que o eixo horizontal é tempo / progresso e a vertical é distância.
Eu acho que você pode fazer uma curva cúbica de Bezier com o primeiro ponto em (0, 1) e o quarto (último) ponto em (1, 0). Os dois pontos médios podem ser colocados aleatoriamente (x = rand, y = rand) dentro desse retângulo 1 por 1. Não consigo verificar isso analiticamente, mas apenas brincando com um applet (sim, vá em frente e ria) parece que a curva de Bezier nunca diminuirá com essa restrição.
Essa será sua função elementar b (p1, p2), que fornece um caminho não decrescente do ponto p1 ao ponto p2.
Agora você pode gerar ab (p (1) = (0, 1), p (n) = (1, 0)) e selecionar um número de p (i) ao longo dessa curva, de modo que 1
Essencialmente, você está gerando um caminho "geral" e, em seguida, dividindo-o em segmentos e regenerando cada segmento.
Como você deseja uma função matemática: Suponha que o procedimento acima seja empacotado em uma função y = f (t, s), que fornece a distância em t para a função de semente s. Você precisará de:
- 4 números aleatórios para colocar os 2 pontos do meio da spline principal de Bezier (de (0, 1) a (1, 0))
- números n-1 para os limites de cada segmento se você tiver n segmentos (o primeiro segmento sempre começa em (0, 1) ou seja, t = 0 e o último termina em (1,0) ou seja, = 1)
- 1 número se você deseja aleatoriamente o número de segmentos
- Mais 4 números para colocar os pontos médios do spline do segmento em que seu t atinge
Portanto, cada semente deve fornecer um dos seguintes:
- 7 + n números reais entre 0 e 1 (se você deseja controlar o número de segmentos)
- 7 números reais e um número inteiro maior que 1 (para um número aleatório de segmentos)
Eu imagino que você pode realizar qualquer um desses simplesmente fornecendo uma matriz de números como a semente s. Como alternativa, você pode fornecer algo como um número s como semente e, em seguida, chamar o gerador de números aleatórios interno com rand (s), rand (s + 1), rand (s + 2) e assim por diante (ou inicializar com se continuar chamando rand.NextNumber).
Observe que, embora toda a função f (t, s) seja composta de muitos segmentos, você está avaliando apenas um segmento para cada t. Você vai precisar para calcular repetidamente os limites de segmentos com este método, porque você vai ter que classificá-los para se certificar que não há dois segmentos se sobrepõem. Você provavelmente pode otimizar e se livrar desse trabalho extra e encontrar apenas os pontos finais de um segmento para cada chamada, mas isso não é óbvio para mim no momento.
Além disso, as curvas de Bezier não são necessárias; qualquer spline com comportamento adequado servirá.
Criei uma implementação de amostra do Matlab.
A função de Bezier (vetorizada):
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
A função composta de Bezier descrita acima (deliberadamente deixada sem vetor para esclarecer quanta avaliação é necessária para cada chamada):
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
O script que plota a função para uma semente aleatória (observe que este é o único local em que uma função aleatória é chamada, as variáveis aleatórias para todos os outros códigos são propagadas a partir dessa matriz aleatória):
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');
Aqui está um exemplo de saída:
Parece atender a maioria dos seus critérios. Contudo:
- Existem "cantos". Isso pode ser possível usando as curvas de Bezier de maneira mais apropriada.
- "Obviamente" parece splines, embora você não possa adivinhar o que fará depois de um período não trivial, a menos que conheça a semente.
- Ele raramente se desvia demais para o canto (pode ser corrigido brincando com a distribuição do gerador de sementes).
- A função Bezier cúbica não pode alcançar uma área próxima à esquina, dadas essas restrições.
f'(x)>0
, de modo que a integração normalizada do valor absoluto de qualquer função de ruído cumpra todos os seus requisitos. Infelizmente, não conheço nenhuma maneira fácil de calcular isso, mas talvez alguém o faça. :)