对于此帖子,y = f(t),其中t是您要更改的参数(时间/进度),y是到目标的距离。因此,我将根据2D绘图上的点进行发言,其中水平轴是时间/进度,垂直轴是距离。
我认为您可以制作三次贝塞尔曲线,第一个点位于(0,1),第四个点(最后)位于(1,0)。可以将两个中间点随机放置在此1×1矩形内(x = rand,y = rand)。我无法通过分析来验证这一点,但是仅仅通过玩一个applet(是的,继续笑),似乎Bezier曲线将永远不会因这种限制而减小。
这将是您的基本函数b(p1,p2),它提供了从点p1到点p2的非递减路径。
现在您可以生成ab(p(1)=(0,1),p(n)=(1,0))并沿该曲线选取多个p(i)使得1
本质上,您将生成一个“通用”路径,然后将其分解为多个段并重新生成每个段。
由于您需要一个数学函数:假设上述过程打包为一个函数y = f(t,s),该函数为种子s的函数提供了t处的距离。你会需要:
- 4个随机数,用于放置主Bezier样条曲线的2个中点(从(0,1)到(1,0))
- 如果您有n个段,则n-1个数字代表每个段的边界(第一个段始终从(0,1)开始,即t = 0,最后一个段从(1,0)开始,即t = 1)
- 如果要随机化段数,则为1个数字
- 还有4个数字,用于放置t线段样条的中点
因此,每个种子必须提供以下之一:
- 0到1之间的7 + n个实数(如果要控制段数)
- 7个实数和一个大于1的整数(对于随机数的段)
我想您可以通过简单地提供一个数字数组作为种子s来完成上述任一操作。或者,您可以执行一些操作,例如提供一个数字s作为种子,然后使用rand(s),rand(s + 1),rand(s + 2)等调用内置的随机数生成器(或初始化为s,然后继续调用rand.NextNumber)。
请注意,即使整个函数f(t,s)由许多段组成,您也只需要为每个t评估一个段。您将需要使用此方法重复计算线段的边界,因为您必须对它们进行排序以确保没有两个线段重叠。您可能可以优化并摆脱这些额外的工作,只为每个调用找到一个分段的端点,但是对我而言这现在并不明显。
同样,贝塞尔曲线不是必需的,任何适当的样条曲线都可以。
我创建了一个示例Matlab实现。
Bezier函数(矢量化):
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
上面描述的复合Bezier函数(故意不进行矢量化处理,以使每个调用都需要进行多少评估):
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
,因此任何噪声函数的绝对值的归一化积分将满足您的所有要求。不幸的是,我不知道有什么简单的方法可以计算出来,但是也许有人可以做到。:)