холст "случайных" изогнутых форм - PullRequest
5 голосов
/ 08 декабря 2011

Я хочу нарисовать случайные изогнутые капли на холсте, но я не могу придумать алгоритм для этого. Я попытался создать случайные кривые Безье, как это:

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();

Проблема, с которой я столкнулся сейчас, заключается в том, что контур фигуры часто пересекает сам себя, что выглядит неправильно. Единственный способ решить эту проблему - отслеживать ограничивающий прямоугольник, и каждая новая точка всегда должна выходить за пределы ограничивающего прямоугольника. Это сложно, потому что вычисление доступного угла добавляет целый уровень сложности.

1 Ответ

3 голосов
/ 08 декабря 2011

Одной из возможностей будет использование полярных координат, а радиус будет функцией угла.Для гладких больших двоичных объектов вы хотите, чтобы радиус был гладким и имел одинаковое значение в 0 и 2 * pi, что можно сделать с помощью тригонометрического полинома:

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

, где коэффициенты являются «случайными».Чтобы контролировать, насколько велик и мал получаемый радиус, вы можете найти максимальное и минимальное значения функции радиуса, а затем сместить и масштабировать коэффициенты соответствующим образом (т. Е. Если вы хотите rlo <= r <= rhi, и нашли min и max,затем замените каждый коэффициент a + b * оригинальным, где b = (rhi-rlo) / (max-min) и a = rlo-b * min). </p>

...