画布上的“随机”曲线形状

6

我想在画布上绘制看起来随机的曲线斑点,但我好像无法想出算法来实现。我尝试创建随机贝塞尔曲线,就像这样:

context.beginPath();
// Each shape should be made up of between three and six curves
var i = random(3, 6);
var startPos = {
    x : random(0, canvas.width),
    y : random(0, canvas.height)
};
context.moveTo(startPos.x, startPos.y);
while (i--) {
    angle = random(0, 360);
    // each line shouldn't be too long
    length = random(0, canvas.width / 5);
    endPos = getLineEndPoint(startPos, length, angle);
    bezier1Angle = random(angle - 90, angle + 90) % 360;
    bezier2Angle = (180 + random(angle - 90, angle + 90)) % 360;
    bezier1Length = random(0, length / 2);
    bezier2Length = random(0, length / 2);
    bezier1Pos = getLineEndPoint(startPos, bezier1Length, bezier1Angle);
    bezier2Pos = getLineEndPoint(endPos, bezier2Length, bezier2Angle);
    context.bezierCurveTo(
        bezier1Pos.x, bezier1Pos.y
        bezier2Pos.x, bezier2Pos.y
        endPos.x, endPos.y
    );
    startPos = endPos;
}

(这是一种简化方法...我添加了一些约束线条只在画布内部等内容。) 这种方法的问题在于如何让它回到起点,而且不会产生大量尴尬的拐角。是否有人知道更好的算法来实现这个,或者可以想出一个?

编辑:我已经有了一些进展。我重新开始,使用直线(一旦完成这一步,我认为我知道该怎么做才能将它们转换成平滑的贝塞尔曲线)。在绘制每个点之前,它会计算出到前一个点的距离和角度与起点的距离和角度。如果距离小于一定值,就会闭合曲线。否则,可能的角度会随着迭代次数变窄,最大线长是到起点的距离。所以这里有一些代码。

start = {
    // start somewhere within the canvas element
    x: random(canvas.width),
    y: random(canvas.height) 
};
context.moveTo(start.x, start.y);
prev = {};
prev.length = random(minLineLength, maxLineLength);
prev.angle = random(360);
prev.x = start.x + prev.length * Math.cos(prev.angle);
prev.y = start.y + prev.length * Math.sin(prev.angle);

j = 1;

keepGoing = true;
while (keepGoing) {
    j++;
    distanceBackToStart = Math.round(
        Math.sqrt(Math.pow(prev.x - start.x, 2) + Math.pow(prev.y - start.y, 2)));
    angleBackToStart = (Math.atan((prev.y - start.y) / (prev.x - start.x)) * 180 / Math.pi) % 360;
    if (isNaN(angleBackToStart)) {
        angleBackToStart = random(360);
    }
    current = {};
    if (distanceBackToStart > minLineLength) {
        current.length = random(minLineLength, distanceBackToStart);
        current.angle = random(angleBackToStart - 90 / j, angleBackToStart + 90 / j) % 360;
        current.x = prev.x + current.length * Math.cos(current.angle);
        current.y = prev.y + current.length * Math.sin(current.angle);
        prev = current;
    } else {
        // if there's only a short distance back to the start, join up the curve
        current.length = distanceBackToStart;
        current.angle = angleBackToStart;
        current.x = start.x;
        current.y = start.y;
        keepGoing = false;
    }
    context.lineTo(current.x, current.y);
}
console.log('Shape complexity: ' + j);
context.closePath();
context.fillStyle = 'black';
context.shadowColor = 'black';
context.shadowOffsetX = -xOffset;
context.shadowOffsetY = -yOffset;
context.shadowBlur = 50;
context.fill();

我现在遇到的问题是,形状的轮廓经常交叉,看起来不正常。我能想到的解决方法是跟踪边界框,每个新点应该始终朝着边界框外部前进。这很棘手,因为计算可用角度会增加整个复杂度。


你应该发布一个完整的HTML页面,包含JavaScript内联。 - Fantius
1
https://dev59.com/GU7Sa4cB1Zd3GeqP1Swv - Fantius
这里有一个 jsfiddle,包含他的代码:http://jsfiddle.net/EnZX4/ - James Clark
感谢@Fantius。请查看我的编辑以获取进展更新。 - Nathan MacInnes
谢谢@JamesClark,我已经用我的更新更新了链接:http://jsfiddle.net/EnZX4/1/ - Nathan MacInnes
1个回答

3

一种可能的方法是使用极坐标,让半径成为角度的函数。对于平滑的斑点,您希望半径平滑,并且在0和2*pi处具有相同的值,这可以通过使用三角多项式来实现:

radius(theta) = a_0 + a_1*sin(theta) + a_2*sin(2*theta) + ... + b_1*cos(theta) + ...

系数是“随机”的。为了控制半径的大小,您可以搜索半径函数的最大值和最小值,然后适当地移动和缩放系数(即,如果您想要rlo≤r≤rhi,并找到了最小值和最大值,则替换每个系数a + b * original,其中b =(rhi-rlo)/(max-min),a = rlo-b * min)。


这是一个很棒的想法!我会试着玩一下,看看能否让它生效。 - Nathan MacInnes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接