随机生成弯曲/波浪路径

时间:2019-01-05 06:56:28

标签: algorithm canvas path html5-canvas

我有一张巨大的地图图像,该图像比视口大得多并且位于视口中心,用户可以通过拖动屏幕来对其进行浏览。为了产生视差效果,我在前景中使用了大量的云层图像。当用户通过拖动浏览地图时,背景和前景都以视差方式移动。到目前为止,一切都很好。

但是,我真正想做的是给云图像一个“默认”运动,该运动将在每次页面加载时随机生成,这样即使用户没有拖动,云也将一直在移动。我知道可以通过对路径上的前景进行动画处理来完成此操作,但是我不确定如何执行此操作。

如何在每次加载页面时随机生成 不规则弯曲 波浪 路径?

有人知道有什么算法可以做到这一点吗?

4 个答案:

答案 0 :(得分:4)

我还使用先前答案的副本来实现我在评论中所暗示内容的简化版本。

在单位圆(即角度)上随机行走,以确定速度矢量,该速度矢量缓慢但随机变化,并使用三次Bezier斑块向前移动。

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>

答案 1 :(得分:3)

如果您的问题是:如何随机生成弯曲或波浪形路径?这就是我的处理方式:我正在使用输入类型范围来更改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>

答案 2 :(得分:3)

能够在SO答案中绘制画布的功能给我留下了深刻的印象,所以我“偷走了” enxaneta 代码段并对其进行了一些尝试(希望可以)。

这个想法是生成几个随机点Rollback,并为路径中的每个(xs, ys)生成y,以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}

答案 3 :(得分:1)

确定性随机路径

不需要存储随机运动的路径。同样,随机是另一种非常复杂的方式,对于人类来说,看起来随机并不需要太多的复杂性。

因此,随机性会增加复杂性,您可以使无限的非重复序列出现,然后将其倒回,停止,减慢速度,并具有完全确定性,并且只需要存储一个值即可。

复杂的循环。

要在圆心周围移动一个点,可以使用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>

接下来,使用上述方法移动旋转点。 然后对于球,我们可以将y的旋转相位更改为x的旋转相位。这意味着球绕当前旋转点旋转,并且球旋转轴异相。

结果是动作更加复杂。

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的种子来创建云。这意味着有一千万个独特的云,但是全部都是可重复的。

确定性随机云的例子

重新启动,它将重复完全相同的操作。要将其更改为非确定性随机,只需添加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>