Using Bezier curve to realize water wave ball

Keywords: Javascript

Preface

Recently, in order to achieve the effect of a water polo, I searched it on the Internet. It is found that most of the implementation methods on the Internet are to use the sine cosine formula to draw points, and then connect them into curves. I feel that it's a bit troublesome to use the sine cosine method. There is a Bezier curve function in Canvas that can draw smooth curves to simulate water waves, so I did it myself. The effect is as follows https://codepen.io/geeknoble/pen/PooQzwQ

Implementation ideas

The water wave sphere consists of a circle and a wave region, and the emphasis is on the realization of the wave region. The wave region can be drawn as a rectangle first, then the upper edge can be linked with a Bezier curve, and then the region and the circle can be cut to get a water wave ball figure, as shown in the figure:

post-crop

First, use Canvas to draw a circle with the center as the center and 1 / 2 of the width as the radius.

function drawCircle(ctx, mW, color) {
      ctx.beginPath();
      ctx.strokeStyle = color;
      // Center as dot, 1 / 2 side length as radius
      ctx.arc(mW / 2, mW / 2, mW / 2 - 1, 0, 2 * Math.PI);
      ctx.stroke();
      ctx.beginPath();
      ctx.arc(mW / 2, mW / 2, mW / 2 - 2, 0, 2 * Math.PI);
      ctx.clip();
    }
    var canvas1 = document.getElementById('canvas')

      var mW = canvas1.clientWidth;
      // console.log(mW);
      // Set the height of the Canvas element
      canvas1.style.height = mW;
      // Set the width and height of Canvas
      canvas1.width = canvas1.height = mW;
      drawCircle(ctx1, mW, '#1a4768');

Then use Bessel function to draw the curve. Bessel curve has cubic function and quadratic function. If we draw a static water wave ball, we can use the cubic Bezier curve. Two control points, one up and one down, can draw a curve similar to the water wave. But if there is animation, it's not good, so the curve drawn 1 / 4 and 1 / 2 are not symmetrical, it will feel very twisted when moving. With animation, you can draw two quadratic Bezier curves, so as to draw symmetrical curves before and after.

function drawCurve(ctx, mW, color, wav, dY) {
      ctx.save();
      ctx.beginPath();
      ctx.moveTo(0, mW);
      ctx.lineTo(0, dY);
      // Draw a cubic Bezier curve
      //ctx.bezierCurveTo(mW / 4, dY - wav, mW * 3/4, dY + wav, mW, dY)
      // Two quadratic Bezier curves need to be drawn
      ctx.quadraticCurveTo(mW / 4, dY - wav, mW / 2, dY);
      ctx.lineTo(mW / 2, dY)
      ctx.quadraticCurveTo((mW * 3) / 4, dY + wav, mW, dY);
      ctx.lineTo(mW, mW);
      ctx.lineTo(0, mW);
      ctx.fillStyle = color;
      ctx.fill();
      ctx.restore();
    }
    ...
    drawCircle(ctx1, mW, '#1a4768');
    drawCurve(ctx2, mW, '#1c86d1', wave, mW - mW * rate);


(drawn by cubic Bezier curve)


(drawn by quadratic Bezier curve)

Animation implementation

The static wave is available, and then it is allowed to move. As long as you move the wave area horizontally, you can see the dynamic effect. Here I use off screen Canvas to draw animation, because it is more convenient to achieve, and the performance overhead is small. The way to achieve this is to create a Canvas circle, another Canvas wave area, and draw continuously with drawImage method.

var canvas1 = document.getElementById('canvas')

      var mW = canvas1.clientWidth;
      // console.log(mW);
      // Set the height of the Canvas element
      canvas1.style.height = mW;
      // Set the width and height of Canvas
      canvas1.width = canvas1.height = mW;

      var canvas2 = document.createElement('canvas'),
        ctx2 = canvas2.getContext('2d');
      canvas2.width = mW;
      canvas2.height = mW;
      
      drawCircle(ctx1, mW, '#1a4768');
      drawCurve(ctx2, mW, '#1c86d1', wave, mW - mW * rate);
      
      function animation() {

        ctx1.clearRect(0, 0, mW, mW)
        
        // Here, I want to draw two to avoid the blank
        ctx1.drawImage(canvas2, x, 0)
        ctx1.drawImage(canvas2, x - mW , 0)
       
        // Boundary judgement
        x >= (mW - speed) ? x = 0 : x += speed
        requestAnimationFrame(animation)
      }
      animation()

It looks good now, but if you look closely, you can see that there is only one cycle curve in the ball. How can I make the wave longer? This can be realized by a feature of drawImage. The size of the cropped image is the size of the image itself under three parameters of drawImage. The last two parameters of drawImage can control the size of the cropped image. In fact, ctx.drawImage(canvas, 0, 0) is equivalent to ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height). The last two parameters can be used for the cropped image Zoom the picture. Then increase x and the wave will be stretched.

...
var flat = 300
function animation() {

        ctx1.clearRect(0, 0, mW, mW)
        
        // Here, I want to draw two to avoid the blank
        ctx1.drawImage(canvas2, x, 0, mW + flat, mW)
        ctx1.drawImage(canvas2, x - mW - flat, 0, mW + flat, mW)
       
        // Boundary judgement
        x >= (mW - speed + flat) ? x = 0 : x += speed
        requestAnimationFrame(animation)
      }
      animation()


If you want to add another wave, add another Canvas, draw a different color, and add a deviation value on the x axis.

function animation() {

        ctx1.clearRect(0, 0, mW, mW)
        
        ctx1.drawImage(canvas2, x, 0, mW + flat, mW)
        ctx1.drawImage(canvas2, x - mW - flat, 0, mW + flat, mW)
        ctx1.drawImage(canvas3, x + distance, 0, mW + flat, mW)
        ctx1.drawImage(canvas3, x - mW + distance - flat, 0, mW + flat, mW)
       
        // Boundary judgement
        x >= (mW - speed + flat) ? x = 0 : x += speed
        requestAnimationFrame(animation)
      }
      animation()

Posted by PHPQuack on Mon, 18 Nov 2019 01:14:25 -0800