Генерация случайных кривых / волнистых контуров - PullRequest
0 голосов
/ 05 января 2019

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

Однако то, что я действительно хочу сделать, это дать изображению облаков движение «по умолчанию», которое будет генерироваться случайным образом при каждой загрузке страницы, чтобы облака всегда перемещались, даже если пользователь не перетаскивает. Я знаю, что это можно сделать, анимируя передний план вдоль пути, но я не совсем уверен, как это сделать.

Как можно случайным образом генерировать нерегулярно изогнутых или волнистых путей на каждой странице загрузки?

Кто-нибудь знает какие-нибудь алгоритмы, которые могут это сделать?

Ответы [ 4 ]

0 голосов
/ 05 января 2019

детерминированные случайные пути

Хранение путей для случайных движений не требуется. Также случайным является еще один способ быть очень сложным, и для людей не требуется много сложностей, чтобы искать случайных людей.

Таким образом, с небольшой случайностью, добавляемой к сложности, вы можете создать бесконечную неповторяющуюся последовательность, которая будет перематываться, останавливаться, замедляться, ускоряться, быть полностью детерминированной и требовать только одного значения для хранения.

Сложные циклы.

Чтобы переместить точку в круге вокруг центра, вы можете использовать sin и cos.

Например, точка x,y, и вы хотите перемещать шарик вокруг этой точки на расстояние dist и скорость раз в секунду. Пример во фрагменте.

var px = 100; // point of rotation.
var py = 100;
const RPS = 1; // Rotations Per Second
const dist = 50; // distance from point
const radius = 25; // circle radius

function moveObj(time) { // Find rotated point and draw    
    time = (time / 1000) * PI2 *  RPS;  // convert the time to rotations per secon    
    const xx = Math.cos(time) * dist;
    const yy = Math.sin(time) * dist;       
    drawCircle(xx, yy) 
}






// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
    ctx.setTransform(1,0,0,1,px,py);
    ctx.fillStyle = "#fff";
    ctx.beginPath();
    ctx.arc(x,y,r,0,PI2);
    ctx.fill();
}
function mainLoop(time) {
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);    
    moveObj(time);         
    requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
   background : #8AF;
   border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>

Далее давайте переместим точку, вокруг которой мы вращаемся, используя метод выше. Тогда для шара мы можем изменить фазу вращения по x от вращения по y. Это означает, что шар вращается вокруг вращающейся точки, а ось вращения шариков не в фазе.

В результате получаются более сложные движения.

var px = 100; // point of rotation.
var py = 100;
const RPS_P = 0.1; // point Rotations Per Second 0.1 every 10 seconds
const RPS_X = 1; // Rotations Per Second in x axis of circle
const RPS_Y = 0.8; // Rotations Per Second in y axis of circle
const dist_P = 30; // distance from center point is
const dist = 50; // distance from point
const radius = 25; // circle radius

function moveObj(time) { // Find rotated point and draw    
    var phaseX = (time / 1000) * PI2 * RPS_X;
    var phaseY = (time / 1000) * PI2 * RPS_Y;
    const xx = Math.cos(phaseX) * dist;
    const yy = Math.sin(phaseY) * dist;       
    drawCircle(xx, yy) 
}

function movePoint(time) { // move point around center
    time = (time / 1000) * PI2 *  RPS_P; 
    px = 100 + Math.cos(time) * dist_P;
    py = 100 + Math.sin(time) * dist_P;       
 
}


// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
    ctx.setTransform(1,0,0,1,px,py);
    ctx.fillStyle = "#fff";
    ctx.beginPath();
    ctx.arc(x,y,r,0,PI2);
    ctx.fill();
}
function mainLoop(time) {
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);    
    movePoint(time);
    moveObj(time);         
    requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
   background : #8AF;
   border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>

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

var px = 100; // point of rotation.
var py = 100;
const RPS_C_X = 0.43; // Rotation speed X of rotating rotation  point
const RPS_C_Y = 0.47; // Rotation speed X of rotating rotation  point
const RPS_P_X = 0.093; // point Rotations speed X
const RPS_P_Y = 0.097; // point Rotations speed Y
const RPS_X = 1; // Rotations Per Second in x axis of circle
const RPS_Y = 0.8; // Rotations Per Second in y axis of circle
const dist_C = 20; // distance from center point is
const dist_P = 30; // distance from center point is
const dist = 30; // distance from point
const radius = 25; // circle radius

function moveObj(time) { // Find rotated point and draw    
    var phaseX = (time / 1000) * PI2 * RPS_X;
    var phaseY = (time / 1000) * PI2 * RPS_Y;
    const xx = Math.cos(phaseX) * dist;
    const yy = Math.sin(phaseY) * dist;       
    drawCircle(xx, yy) 
}

function movePoints(time) { // Move the rotating pointe and rotate the rotation point 
                            // around that point

    var phaseX = (time / 1000) * PI2 * RPS_C_X;
    var phaseY = (time / 1000) * PI2 * RPS_C_Y;
    px = 100 + Math.cos(phaseX) * dist_C;
    py = 100 + Math.sin(phaseY) * dist_C;  

    phaseX = (time / 1000) * PI2 * RPS_P_X;
    phaseY = (time / 1000) * PI2 * RPS_P_Y;
    px = px + Math.cos(phaseX) * dist_P;
    py = py + Math.sin(phaseY) * dist_P;       
 
}


// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
    ctx.setTransform(1,0,0,1,px,py);
    ctx.fillStyle = "#fff";
    ctx.beginPath();
    ctx.arc(x,y,r,0,PI2);
    ctx.fill();
}
function mainLoop(time) {
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);    
    movePoints(time);
    moveObj(time);         
    requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
   background : #8AF;
   border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>

Так что теперь у нас очень сложное вращение. Однако, поскольку оно установлено на время, вы можете повторить движение, просто установив время обратно на старт. Вам не нужно хранить длинный сложный путь.

Добавить немного случайных

Вы можете увидеть некоторое повторяющееся движение, но если вы сделаете фазы каждой оси простыми, тогда время повторения будет произведением всех простых чисел.

Если вам нужно много объектов, каждый с разным движением, вы можете рандомизировать скорости вращения и многие другие свойства.

В Javascript нет сеяного генератора случайных чисел. Однако вы можете создать один. С генератором случайных чисел с семенами вы можете использовать зерно для генерации случайного объекта. Но если вы снова используете это семя, вы получите тот же объект. В приведенном ниже примере я использовал начальное значение от 0 до 10000000 для создания облака. Это означает, что есть 10000000 уникальных облаков, но все повторяются.

Пример детерминированных случайных облаков

Перезагрузите компьютер, и он будет повторяться точно так же. Чтобы изменить его на недетерминированный случайный, просто добавьте randSeed(Math.random() * 100000 | 0)

const seededRandom = (() => {
    var seed = 1;
    return { max : 2576436549074795, reseed (s) { seed = s }, random ()  { return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max }}
})();
const randSeed = (seed) => seededRandom.reseed(seed|0);
const randSI = (min = 2, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS  = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
const randSPow  = (min, max = min + (min = 0), p = 2) => (max + min) / 2 + (Math.pow(seededRandom.random() / seededRandom.max, p) * (max - min) * 0.5) * (randSI(2) < 1 ? 1 : -1);

const ctx = canvas.getContext("2d");
const W = ctx.canvas.width;
const H = ctx.canvas.height;
const DIAG = (W * W + H * H) ** 0.5;
const colors = {
    dark : {
        minRGB : [100 * 0.6,200 * 0.6,240 * 0.6],
        maxRGB : [255 * 0.6,255 * 0.6,255 * 0.6],
    },
    light : {
        minRGB : [100,200,240],
        maxRGB : [255,255,255],
    },
}
const getCol = (pos, range) => "rgba(" +
    ((range.maxRGB[0] - range.minRGB[0]) * pos + range.minRGB[0] | 0) + "," + 
    ((range.maxRGB[1] - range.minRGB[1]) * pos + range.minRGB[1] | 0) + "," + 
    ((range.maxRGB[2] - range.minRGB[2]) * pos + range.minRGB[2] | 0) + "," +(pos * 0.2 + 0.8) + ")";


const Cloud = {
    x : 0,
    y : 0,
    dir : 0, // in radians
    wobble : 0,
    wobble1 : 0,
    wSpeed : 0,
    wSpeed1 : 0,
    mx : 0,  // Move offsets
    my : 0,
    seed : 0,
    size : 2, 
    detail : null,
    reset : true, // when true could resets
    init() {
        this.seed = randSI(10000000);
        this.reset = false;
        var x,y,r,dir,dist,f;
        if (this.detail === null) { this.detail = [] }
        else { this.detail.length = 0 }
        randSeed(this.seed);
        this.size = randSPow(2, 8);  // The pow add bias to smaller values
        var col = (this.size -2) / 6;
        this.col1 = getCol(col,colors.dark)
        this.col2 = getCol(col,colors.light)
        var flufCount = randSI(5,15);
        while (flufCount--) {
            x = randSI(-this.size * 8, this.size * 8);
            r = randS(this.size * 2, this.size * 8);
            dir = randS(Math.PI * 2);
            dist = randSPow(1) * r ;
            this.detail.push(f = {x,r,y : 0,mx:0,my:0, move : randS(0.001,0.01), phase : randS(Math.PI * 2)});
            f.x+= Math.cos(dir) * dist;
            f.y+= Math.sin(dir) * dist;

        }
        this.xMax = this.size * 12 + this.size * 10 + this.size * 4;
        this.yMax = this.size * 10 + this.size * 4;
        this.wobble = randS(Math.PI * 2);
        this.wSpeed = randS(0.01,0.02);
        this.wSpeed1 = randS(0.01,0.02);
        const aOff = randS(1) * Math.PI * 0.5 - Math.PI *0.25;
        this.x = W / 2 - Math.cos(this.dir+aOff) * DIAG * 0.7;
        this.y = H / 2 - Math.sin(this.dir+aOff) * DIAG * 0.7;
        clouds.sortMe = true; // flag that coulds need resort
    },
    move() {
        var dx,dy;
        this.dir = gTime / 10000;
        if(this.reset) { this.init() }
        this.wobble += this.wSpeed;
        this.wobble1 += this.wSpeed1;
        this.mx = Math.cos(this.wobble) * this.size * 4;
        this.my = Math.sin(this.wobble1) * this.size * 4;
        this.x += dx = Math.cos(this.dir) * this.size / 5;
        this.y += dy = Math.sin(this.dir) * this.size / 5;
        if (dx > 0 && this.x > W + this.xMax ) { this.reset = true }
        else if (dx < 0 && this.x < - this.xMax ) { this.reset = true }
        if (dy > 0 && this.y > H + this.yMax) { this.reset = true }
        else if (dy < 0 && this.y < - this.yMax) { this.reset = true }

        
    },
    draw(){
        const s = this.size;
        const s8 = this.size * 8;
        ctx.fillStyle = this.col1;
        ctx.setTransform(1,0,0,1,this.x+ this.mx,this.y +this.my);
        ctx.beginPath();
        for (const fluf of this.detail) {
            fluf.phase += fluf.move + Math.sin(this.wobble * this.wSpeed1) * 0.02 *  Math.cos(fluf.phase);
            fluf.mx = Math.cos(fluf.phase) * fluf.r / 2;
            fluf.my = Math.sin(fluf.phase) * fluf.r / 2;
            const x = fluf.x + fluf.mx;
            const y = fluf.y + fluf.my;
            ctx.moveTo(x + fluf.r + s, y);
            ctx.arc(x,y,fluf.r+ s,0,Math.PI * 2);
        }
        ctx.fill();
        ctx.fillStyle = this.col2;
        ctx.globalAlpha = 0.5;
        ctx.beginPath();
        for (const fluf of this.detail) {
            const x = fluf.x + fluf.mx - s;
            const y = fluf.y + fluf.my - s;
            ctx.moveTo(x + fluf.r, y);
            ctx.arc(x,y,fluf.r,0,Math.PI * 2);
        }
        ctx.fill();
        ctx.globalAlpha = 0.6;
        ctx.beginPath();
        for (const fluf of this.detail) {
            const x = fluf.x + fluf.mx - s * 1.4;
            const y = fluf.y + fluf.my - s * 1.4;
            ctx.moveTo(x + fluf.r * 0.8, y);
            ctx.arc(x,y,fluf.r* 0.8,0,Math.PI * 2);
        }
        ctx.fill();        
        ctx.globalAlpha = 1;
    }
}

function createCloud(size){ return {...Cloud} }

const clouds = Object.assign([],{
    move() { for(const cloud of this){ cloud.move() } },
    draw() { for(const cloud of this){ cloud.draw() } },
    sortMe : true, // if true then needs to resort
    resort() { 
        this.sortMe = false;
        this.sort((a,b)=>a.size - b.size);
    }
});
for(let i = 0; i < 15; i ++) { clouds.push(createCloud(40)) }
requestAnimationFrame(mainLoop)
var gTime = 0;
function mainLoop() {
    gTime += 16;
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
    if(clouds.sortMe) { clouds.resort() }
    clouds.move();
    clouds.draw();

    requestAnimationFrame(mainLoop);

}
body { padding : 0px; margin : 0px;}
canvas {
   background : rgb(60,120,148);
   border : 1px solid black;
}
<canvas id="canvas" width="600" height="200"></canvas>
0 голосов
/ 05 января 2019

Я был впечатлен функциональностью, позволяющей рисовать холсты в SO-ответах, поэтому я "украл" enxaneta фрагмент кода и немного поиграл с ним (надеюсь, что все в порядке).

Идея состоит в том, чтобы сгенерировать несколько случайных точек (xs, ys) и для каждого x из пути интерполировать y как y = sum{ys_i*w_i}/sum{w_i}, где w_i - это некоторый вес интерполяции как функция x. Например w_i(x) = (xs_i - x)^(-2). Надеюсь, что это имеет смысл - если это будет интересно, я постараюсь предоставить более подробную информацию.

var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 600;
var ch = c.height = 150;
var cx = cw / 2,
  cy = ch / 2;

var amplitude = a.value;
var frequency = f.value;
ctx.lineWidth = 4;

var npts = 20;
var xs = Array();
var ys = Array();
for (var i = 0; i < npts; i++) {
   xs[i] = (cw/npts)*i; 
   ys[i] = 2.0*(Math.random()-0.5)*amplitude;
}

function Draw() {
  ctx.clearRect(0, 0, cw, ch);
  ctx.beginPath();
 
  for (var x = 0; x < cw; x++) {
    y = 0.0;
    wsum = 0.0;
    for (var i = -5; i <= 5; i++) {
       xx = x;
       ii = Math.round(x/xs[1]) + i;
       if (ii < 0) { xx += cw; ii += npts; }
       if (ii >= npts) { xx -= cw; ii -= npts; }
       w = Math.abs(xs[ii] - xx);
       w = Math.pow(w, frequency); 
       y += w*ys[ii];
       wsum += w;
    }
    y /= wsum;
    //y = Math.sin(x * frequency) * amplitude;
    ctx.lineTo(x, y+cy); 
  }

  ctx.stroke();

}
Draw();

a.addEventListener("input",()=>{
  amplitude = a.value;
  for (var i = 0; i < npts; i++) {
    xs[i] = (cw/npts)*i; 
    ys[i] = 2.0*(Math.random()-0.5)*amplitude;
  }
  Draw();
})

f.addEventListener("input",()=>{
  frequency = f.value;
  Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<p>amplitude: <input type="range" id="a" min ="1" max = "100"  value="50" /></p>
<p>frequency: <input type="range" id="f" min ="-10" max = "1" step = "0.1" value="-2" hidden/></p>
0 голосов
/ 05 января 2019

Я также использую копию предыдущих ответов, чтобы реализовать упрощенную версию того, на что я намекал в комментариях.

Используйте случайное блуждание по единичному кругу, то есть по углу, чтобы определить вектор скорости, который медленно, но случайно меняется, и двигайтесь вперед, используя кубические патчи Безье.

var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 600;
var ch = c.height = 400;
var cx = cw / 4, cy = ch / 2;

var angVel = v.value;
var tension = t.value;
ctx.lineWidth = 4;

var npts = 60;
var dw = Array();
var xs = Array();
var ys = Array();
var vxs = Array();
var vys = Array();

function Randomize() {
    for (var i = 0; i < npts; i++) {
        dw[i] = (2*Math.random()-1);
    }
}

function ComputePath() {
    xs[0]=cx; ys[0]=cy; 
    var angle = 0;
    for (var i = 0; i < npts; i++) {
        vxs[i]=10*Math.cos(2*Math.PI*angle);
        vys[i]=10*Math.sin(2*Math.PI*angle);
        angle = angle + dw[i]*angVel;
    }
    for (var i = 1; i < npts; i++) {
        xs[i] = xs[i-1]+3*(vxs[i-1]+vxs[i])/2; 
        ys[i] = ys[i-1]+3*(vys[i-1]+vys[i])/2;
    }
}

function Draw() {
  ctx.clearRect(0, 0, cw, ch);
  ctx.beginPath();
  ctx.moveTo(xs[0],ys[0]); 
  for (var i = 1; i < npts; i++) {
    var cp1x = xs[i-1]+tension*vxs[i-1];
    var cp1y = ys[i-1]+tension*vys[i-1];
    var cp2x = xs[i]-tension*vxs[i];
    var cp2y = ys[i]-tension*vys[i]
    ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, xs[i], ys[i]); 
  }
  ctx.stroke();
}
Randomize();
ComputePath();
Draw();

r.addEventListener("click",()=>{
  Randomize();
  ComputePath();
  Draw();
})

v.addEventListener("input",()=>{
  angVel = v.value;
  vlabel.innerHTML = ""+angVel;
  ComputePath();
  Draw();
})

t.addEventListener("input",()=>{
  tension = t.value;
  tlabel.innerHTML = ""+tension;
  Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<table>
  <tr><td>angular velocity:</td><td> <input type="range" id="v" min ="0" max = "0.5" step = "0.01" value="0.2" /></td><td id="vlabel"></td></tr>
  <tr><td>tension</td><td> <input type="range" id="t" min ="0" max = "1" step = "0.1" value="0.8" /></td><td id="tlabel"></td></tr>
  <tr><td>remix</td><td> <button id="r"> + </button></td><td></td></tr>
</table>
0 голосов
/ 05 января 2019

Если ваш вопрос: Как я могу случайным образом создать изогнутые или волнистые пути? Вот как я бы это сделал: я использую диапазон типов ввода, чтобы изменить значение для amplitude и frequency, но вы можете установить эти значения случайным образом при загрузке. Я надеюсь, что это помогает.

var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 800;
var ch = c.height = 150;
var cx = cw / 2,
  cy = ch / 2;

var amplitude = a.value;
var frequency = f.value;
ctx.lineWidth = 4;

function Draw() {
  ctx.clearRect(0, 0, cw, ch);
  ctx.beginPath();
 
  for (var x = 0; x < cw; x++) {
    y = Math.sin(x * frequency) * amplitude;
    ctx.lineTo(x, y+cy); 
  }

  ctx.stroke();

}
Draw();

a.addEventListener("input",()=>{
  amplitude = a.value;
  Draw();
})

f.addEventListener("input",()=>{
  frequency = f.value;
  Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<p>frequency: <input type="range" id="f" min ="0.01" max = "0.1" step = "0.001" value=".05" /></p>
<p>amplitude: <input type="range" id="a" min ="1" max = "100"  value="50" /></p>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...