javascript 在画布中制作波浪并制作波浪动画

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19787775/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 16:47:49  来源:igfitidea点击:

Make and animate wave in canvas

javascripthtmlcanvas

提问by Antoine

I'm looking for a way to create a wave in a shape designed in canvas. After much research I found something that is pretty close to what I want:

我正在寻找一种以画布设计的形状创建波浪的方法。经过大量研究,我发现了一些非常接近我想要的东西:

var c = document.getElementById('c'),
  ctx = c.getContext('2d'),
  cw = c.width = window.innerWidth,
  ch = c.height = window.innerHeight,
  points = [],
  tick = 0,
  opt = {
    count: 5,
    range: {
      x: 20,
      y: 80
    },
    duration: {
      min: 20,
      max: 40
    },
    thickness: 10,
    strokeColor: '#444',
    level: .35,
    curved: true
  },
  rand = function(min, max) {
    return Math.floor((Math.random() * (max - min + 1)) + min);
  },
  ease = function(t, b, c, d) {
    if ((t /= d / 2) < 1) return c / 2 * t * t + b;
    return -c / 2 * ((--t) * (t - 2) - 1) + b;
  };

ctx.lineJoin = 'round';
ctx.lineWidth = opt.thickness;
ctx.strokeStyle = opt.strokeColor;

var Point = function(config) {
  this.anchorX = config.x;
  this.anchorY = config.y;
  this.x = config.x;
  this.y = config.y;
  this.setTarget();
};

Point.prototype.setTarget = function() {
  this.initialX = this.x;
  this.initialY = this.y;
  this.targetX = this.anchorX + rand(0, opt.range.x * 2) - opt.range.x;
  this.targetY = this.anchorY + rand(0, opt.range.y * 2) - opt.range.y;
  this.tick = 0;
  this.duration = rand(opt.duration.min, opt.duration.max);
}

Point.prototype.update = function() {
  var dx = this.targetX - this.x;
  var dy = this.targetY - this.y;
  var dist = Math.sqrt(dx * dx + dy * dy);

  if (Math.abs(dist) <= 0) {
    this.setTarget();
  } else {
    var t = this.tick;
    var b = this.initialY;
    var c = this.targetY - this.initialY;
    var d = this.duration;
    this.y = ease(t, b, c, d);

    b = this.initialX;
    c = this.targetX - this.initialX;
    d = this.duration;
    this.x = ease(t, b, c, d);

    this.tick++;
  }
};

Point.prototype.render = function() {
  ctx.beginPath();
  ctx.arc(this.x, this.y, 3, 0, Math.PI * 2, false);
  ctx.fillStyle = '#000';
  ctx.fill();
};

var updatePoints = function() {
  var i = points.length;
  while (i--) {
    points[i].update();
  }
};

var renderPoints = function() {
  var i = points.length;
  while (i--) {
    points[i].render();
  }
};

var renderShape = function() {
  ctx.beginPath();
  var pointCount = points.length;
  ctx.moveTo(points[0].x, points[0].y);
  var i;
  for (i = 0; i < pointCount - 1; i++) {
    var c = (points[i].x + points[i + 1].x) / 2;
    var d = (points[i].y + points[i + 1].y) / 2;
    ctx.quadraticCurveTo(points[i].x, points[i].y, c, d);
  }
  ctx.lineTo(-opt.range.x - opt.thickness, ch + opt.thickness);
  ctx.lineTo(cw + opt.range.x + opt.thickness, ch + opt.thickness);
  ctx.closePath();
  ctx.fillStyle = 'hsl(' + (tick / 2) + ', 80%, 60%)';
  ctx.fill();
  ctx.stroke();
};

var clear = function() {
  ctx.clearRect(0, 0, cw, ch);
};

var loop = function() {
  window.requestAnimFrame(loop, c);
  tick++;
  clear();
  updatePoints();
  renderShape();
  //renderPoints();
};

var i = opt.count + 2;
var spacing = (cw + (opt.range.x * 2)) / (opt.count - 1);
while (i--) {
  points.push(new Point({
    x: (spacing * (i - 1)) - opt.range.x,
    y: ch - (ch * opt.level)
  }));
}

window.requestAnimFrame = function() {
  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(a) {
    window.setTimeout(a, 1E3 / 60)
  }
}();

loop();
canvas {
  display: block;
}
<canvas id="c"></canvas>

http://codepen.io/Hymanrugile/pen/BvLHg

http://codepen.io/Hymanrugile/pen/BvLHg

The problem is that the movement of the wave appears a bit unreal. I'd like to keep this notion of random motion and not have a shape that repeats itself by moving from left to right but it will be great if I found a way to create a ‘realistic' water movement (good fluid dynamics, collisions of this wave with the edges of its container (custom shape)).

问题是波浪的运动看起来有点不真实。我想保留这种随机运动的概念,而不是通过从左到右移动来重复自己的形状,但是如果我找到一种方法来创建“真实”的水运动(良好的流体动力学,碰撞此波浪及其容器的边缘(自定义形状))。

I think I'm asking a lot but ... A small line of research could help :)

我想我问了很多但是......一小部分研究可能会有所帮助:)

回答by

Interference

干涉

You can make a more realistic wave using interference.

您可以使用干涉制作更逼真的波浪。

  • Have one big wave (swell) running slowly with a big motion
  • Have another one or two smaller sine waves running (oscillators)
  • All with random amplitudes
  • Mix the waves horizontally using average and calculate the various points
  • Draw the result using a cardinal spline (or if the resolution is high you can just draw simple lines between the points instead).
  • 让一个大波浪(膨胀)以大动作缓慢运行
  • 运行另外一两个较小的正弦波(振荡器)
  • 全部具有随机幅度
  • 使用平均值水平混合波浪并计算各个点
  • 使用基数样条绘制结果(或者如果分辨率很高,您可以只在点之间绘制简单的线)。

Use various parameters so you can adjust it live to find a good combination.

使用各种参数,以便您可以实时调整它以找到合适的组合。

You can also add oscillators to represent the z axis to make it more realistic in case you want to layer the waves to make a pseudo-3D wave.

您还可以添加振荡器来表示 z 轴,以使其更逼真,以防您想要将波浪分层以制作伪 3D 波浪。

Example

例子

I cannot give you wave collision, fluid dynamics - that would be a bit too broad for SO but I can give you a fluid-ish wave example (as you have the point of each segment you can use that for collision detection).

我不能给你波碰撞、流体动力学——这对 SO 来说有点太宽泛了,但我可以给你一个流体波的例子(因为你有每个段的点,你可以用它来检测碰撞)。

And example would be to create an oscillator object which you could set the various settings on such as amplitude, rotation speed (phase) etc.

例如,创建一个振荡器对象,您可以在其中设置各种设置,例如振幅、旋转速度(相位)等。

Then have a mixer function which mixes the result of these oscillators that you use.

然后有一个混频器功能,可以混合您使用的这些振荡器的结果。

Live demo here(full-screen version here)

现场演示在这里全屏版在这里

Demo snapshot

演示快照

The oscillator object in this demo look like this:

此演示中的振荡器对象如下所示:

function osc() {

    /// various settings        
    this.variation = 0.4;  /// how much variation between random and max
    this.max       = 100;  /// max amplitude (radius)
    this.speed     = 0.02; /// rotation speed (for radians)

    var me  = this,        /// keep reference to 'this' (getMax)
        a   = 0,           /// current angle
        max = getMax();    /// create a temp. current max

    /// this will be called by mixer
    this.getAmp = function() {

        a += this.speed;   /// add to rotation angle

        if (a >= 2.0) {    /// at break, reset (see note)
            a = 0;
            max = getMax();
        }

        /// calculate y position
        return max * Math.sin(a * Math.PI) + this.horizon;
    }

    function getMax() {
        return Math.random() * me.max * me.variation +
               me.max * (1 - me.variation);
    }

    return this;
}

This do all the setup and calculations for us and all we need to do is to call the getAmp()to get a new value for each frame.

这为我们完成了所有的设置和计算,我们需要做的就是调用getAmp()为每一帧获取一个新值。

Instead of doing it manually we can use a "mixer". This mixer allows us to add as many oscillators we want to the mix:

我们可以使用“混合器”代替手动操作。这个混音器允许我们添加任意数量的振荡器到混音中:

function mixer() {

    var d = arguments.length,  /// number of arguments
        i = d,                 /// initialize counter
        sum = 0;               /// sum of y-points

    if (d < 1) return horizon; /// if none, return

    while(i--) sum += arguments[i].getAmp(); /// call getAmp and sum

    return sum / d + horizon;  /// get average and add horizon
}

Putting this in a loop with a point recorder which shifts the point in one direction will create a fluid looking wave.

将其与一个点记录器一起放入一个循环中,该点记录器将点向一个方向移动,将创建一个流动的波浪。

The demo above uses three oscillators. (A tip in that regard is to keep the rotation speed lower than the big swell or else you will get small bumps on it.)

上面的演示使用了三个振荡器。(在这方面的一个提示是保持旋转速度低于大膨胀,否则你会得到小颠簸。)

NOTE: The way I create a new random max is not the best way as I use a break point (but simple for demo purpose). You can instead replace this with something better.

注意:我创建新的随机最大值的方式不是最好的方式,因为我使用了断点(但对于演示目的很简单)。你可以用更好的东西代替它。

回答by GameAlchemist

Since you are searching for a realistic effect, best idea might be to simulate the water. It is not that hard, in fact : water can be nicely enough approximated by a network of springs linked together.

由于您正在寻找逼真的效果,最好的办法可能是模拟水。事实上,这并不难:通过连接在一起的泉水网络可以很好地近似水。

Result is quite good, you can find it here : http://jsfiddle.net/gamealchemist/Z7fs5/

结果相当不错,你可以在这里找到它:http: //jsfiddle.net/gamealchemist/Z7fs5/

enter image description here

在此处输入图片说明

So i assumed it was 2D effect and built an array holding, for each point of a water surface, its acceleration, speed, and position. I store them in a single array, at 3*i + 0, 3*i + 1, and 3*i+2.
Then on each update, i simply apply newton's laws with elasticity, and with some friction to get the movement to stop.
I influence each point so it goes to its stable state + get influenced by its right and left neighboor.
(If you want smoother animation, use also i-2 and i+2 points, and lower kFactor.)

所以我假设它是 2D 效果并构建了一个数组,用于保存水面的每个点的加速度、速度和位置。我将它们存储在一个数组中,分别为 3*i + 0、3*i + 1 和 3*i+2。
然后在每次更新时,我只是简单地应用具有弹性的牛顿定律,并通过一些摩擦来使运动停止。
我影响每个点,使其进入稳定状态+受到左右邻居的影响。
(如果您想要更流畅的动画,还可以使用 i-2 和 i+2 点,以及较低的 kFactor。)

var left = 0, y = -1;
var right = water[2];
for (pt = 0 ; pt < pointCount; pt++, i += 3) {
    y = right;
    right = (pt < pointCount - 1) ? water[i + 5] : 0;
    if (right === undefined) alert('nooo');
    // acceleration
    water[i] = (-0.3 * y + (left - y) + (right - y)) * kFactor - water[i + 1] * friction;
    // speed
    water[i + 1] += water[i] * dt;
    // height
    water[i + 2] += water[i + 1] * dt;
    left = y;
}

The draw is very simple : just iterate though the points and draw. But it's hard to get a smooth effect while drawing, since it's hard to have bezier and quadraticCurve to have their derivates match. I suggested a few drawing methods, you can switch if you want.

绘制非常简单:只需遍历点并绘制即可。但是在绘制时很难获得平滑的效果,因为很难让贝塞尔曲线和二次曲线的导数匹配。我推荐了几种绘图方法,您可以根据需要切换。

Then i added rain, so that the water can move in a random way. Here it's just very simple trajectory, then i compute if there's collision with the water, and, if so, i add some velocity and shift the point.

然后我加了雨,这样水就可以随机移动了。这里只是非常简单的轨迹,然后我计算是否与水发生碰撞,如果是,我增加一些速度并移动点。

回答by Schoening

I'd like to create a ‘realistic' water movement with good fluid dynamics, collisions of this wave with the edges of a custom container..

我想创建一个具有良好流体动力学的“逼真”水运动,该波浪与自定义容器的边缘发生碰撞。

Oh boy.. That is quite a mouthful. You should probably ask your Question here: gamedev.stackexchange

哦,男孩.. 那真是一口。你应该在这里问你的问题:gamedev.stackexchange

Anyways, have you tried to program any sort of wave yet, or are you just looking for WaveCreator.js?

无论如何,您是否尝试过编写任何类型的 wave,或者您只是在寻找WaveCreator.js

Go and Google some Non-language-specific Guideson how to create 2D water.

去谷歌搜索一些关于如何创建 2D 水的非语言特定指南

If you are a beginner, then start with something simple to get the idea behind things. How about creating a bunch of Boxes for "minecraft-style" water ?

如果您是初学者,请从一些简单的事情开始,以了解事物背后的想法。为“我的世界风格”的水制作一堆盒子怎么样?

enter image description here

在此处输入图片说明

Here every "line" of water could be represented as a position in an Array. Then loop through it and set the "height" of the water based on the previous Array Index. (You could smooth the water out by either making the blocks very thin (More work for your program!) or by smoothing out the edges and giving them an angle based on the other Squares.

在这里,每一“线”水都可以表示为数组中的一个位置。然后循环遍历它并根据之前的数组索引设置水的“高度”。(您可以通过使块变得非常薄(为您的程序做更多工作!)或通过平滑边缘并根据其他正方形给它们一个角度来平滑水。

I think this could be a neat solution. Anyhow. Hope that gave you some ideas.

我认为这可能是一个巧妙的解决方案。无论如何。希望能给你一些想法。