在画布上制作和制作波浪

时间:2013-11-05 11:19:37

标签: javascript html5 canvas

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

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/jackrugile/pen/BvLHg

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

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

3 个答案:

答案 0 :(得分:19)

干扰

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

  • 有一个大浪​​(膨胀)慢慢地运动
  • 让另外一个或两个较小的正弦波运行(振荡器)
  • 所有随机振幅
  • 使用平均值水平混合波浪并计算各个点
  • 使用基数样条线绘制结果(或者如果分辨率很高,则可以在点之间绘制简单的线条)。

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

您还可以添加振荡器来表示z轴,以便在您想要将波层叠为伪3D波时更加逼真。

实施例

我不能给你波浪碰撞,流体动力学 - 这对于SO来说有点过于宽泛但是我可以给你一个流动的波形例子(因为你可以使用每个段的点来进行碰撞检测)

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

然后有一个混音器功能,它混合了你使用的这些振荡器的结果。

Live demo herefull-screen version here

Demo snapshot

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

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;
}

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

不是手动操作,而是使用“混音器”。这个混音器允许我们添加我们想要的振荡器:

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
}

将它放在带有点记录器的循环中,点记录器将点向一个方向移动将产生流畅的波浪。

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

注意:我创建一个新的随机最大值的方式并不是我使用断点的最佳方法(但对于演示目的来说很简单)。你可以用更好的东西替换它。

答案 1 :(得分:14)

由于您正在寻找逼真的效果,最好的想法可能是模拟水。实际上并不是那么难:水可以很好地接近连接在一起的弹簧网络。

结果非常好,你可以在这里找到它: http://jsfiddle.net/gamealchemist/Z7fs5/

enter image description here

所以我认为它是二维效果,并为水面的每个点建立一个数组,保持其加速度,速度和位置。我将它们存储在一个阵列中,分别为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;
}

绘制非常简单:只需迭代点并绘制。但是在绘制时很难获得平滑的效果,因为很难让bezier和quadraticCurve使它们的衍生物匹配。我建议了一些绘图方法,你可以根据需要进行切换。

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

答案 2 :(得分:3)

  

我想创造一个具有良好流体动力学的“逼真”水运动,这种波与自定义边缘的碰撞   容器..

哦,小男孩......那真是一口气。 您可以在此处提出您的问题:gamedev.stackexchange

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

Go和Google了解如何创建2D水的非语言特定指南

如果你是初学者,那么从简单的事情开始,就可以了解事情背后的想法。 怎么样为“我的世界式”水创造一堆盒子?

enter image description here

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

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