PK creative Spring Festival, I am participating in the “Spring Festival creative submission contest”, please see: Spring Festival creative submission Contest

preface

Double Olympic linmen, xiangxiangshengju, I do not know that we have seen the opening ceremony of the Winter Olympics, the final flame lit when it is too amazing! Snowflakes converge into the base of the torch, which is amazing. Snowflakes are beautiful because they conform to our naive aesthetic of geometry, where fractals often create impressive patterns.

Speaking of this, we have to briefly introduce fractal. Fractal is a geometry that studies irregular geometric forms and is founded and named by B.B.Mandelbrot and others.

The fractal graph as a whole is irregular everywhere. But from the local observation, the regularity of the graph is the same, that is, it has the property of self-similarity. Isn’t this consistent with the characteristics of snowflakes?

Generally speaking, fractal is defined as iterating a certain geometric shape (metagrams) on its edge to generate a shape that is approximate to the metagrams. This time, based on the principle of branch, draw a snowflake on canvas.

Basic Mathematics

Before drawing snowflakes, let’s simplify the problem. The simplest snowflake is a hexagon, so we can start with the simplest hexagonal snowflake.

As we all know, the coordinate system adopted by Canvas is based on the top left corner of Canvas by default, with X-axis moving to the right and Y-axis moving down. The size of the canvas determines the maximum x and y axis of the coordinate system.

Suppose we want to implement a hexagon on a Canvas that is 300 by 300. The length of the hexagon is 50. Think about what to do.

Look at the basic shape of the hexagon and find the 12 edges. The next two edges are drawn in a group. After drawing the first edge, turn 60 degrees counterclockwise, then draw the second edge of the same length.

Let’s say I’m going from P1 to P2, to P3, to P4. So given the coordinates of P1 and P2, we also need to figure out what the coordinates of P3 and P4 are. This is a simple mathematical problem of finding the next coordinate based on coordinates and angles, and we can plot it by simple vertex conversion.

Rotation and coordinate point mapping

Let’s do a quick math review. The coordinates of P1 and P2 are known, and the length of P2P1 is r. After rotating the vector P1P2 clockwise by the Angle α2, the coordinate value of P3 is calculated.


{ x 3 = x 2 + r c o s ( Alpha. 2 Alpha. 1 ) y 3 = y 2 r s i n ( Alpha. 2 Alpha. 1 ) c o s ( Alpha. 1 ) = x 2 x 1 r s i n ( Alpha. 1 ) = y 2 y 1 r = > { x 3 = x 2 + ( x 2 x 1 ) c o s ( Alpha. 2 ) + ( y 2 y 1 ) s i n ( Alpha. 2 ) y 3 = y 2 ( x 2 x 1 ) s i n ( Alpha. 2 ) + ( y 2 y 1 ) c o s ( Alpha. 2 ) \ begin {cases} x_3 = x_2 + rcos (alpha _2 – alpha _1) \ \ y_3 = y_2 – rsin (alpha _2 – alpha _1) \ \ cos (alpha _1) = \ cfrac {x_2 – x_1} {r} \ \ sin (alpha _1) = \ cfrac {y_2 – y_1} {r} {cases} = > \ \ end begin {cases} x_3 = x_2 + (x_2 – x_1) cos (alpha _2) + (y_2 – y_1) sin (alpha) _2 \ \ y_3 = Y_2 – (x_2 – x_1) sin (alpha _2) + (y_2 – y_1) cos (alpha) _2 \ end {cases}

Similarly, the formula for counterclockwise rotation can be obtained.


{ x 3 = x 2 + r c o s ( Alpha. 2 + Alpha. 1 ) y 3 = y 2 + r s i n ( Alpha. 2 + Alpha. 1 ) c o s ( Alpha. 1 ) = x 2 x 1 r s i n ( Alpha. 1 ) = y 2 y 1 r = > { x 3 = x 2 + ( x 2 x 1 ) c o s ( Alpha. 2 ) ( y 2 y 1 ) s i n ( Alpha. 2 ) y 3 = y 2 + ( y 2 y 1 ) c o s ( Alpha. 2 ) + ( x 2 x 1 ) s i n ( Alpha. 2 ) \ begin {cases} x_3 = x_2 + rcos (alpha + alpha _1, _2) \ \ y_3 = y_2 + rsin (alpha + alpha _1, _2) \ \ cos (alpha _1) = \ cfrac {x_2 – x_1} {r} \ \ sin (alpha _1) = \ cfrac {y_2 – y_1} {r} {cases} = > \ \ end begin {cases} x_3 = x_2 + (x_2 – x_1) cos (alpha _2) – (y_2 – y_1) sin (alpha) _2 \ \ y_3 = Y_2 + (y_2 – y_1) cos (alpha _2) + (x_2 – x_1) sin (alpha) _2 \ end {cases}

The rotation matrix can also be obtained from the above deduction as follows.

clockwise


[ c o s Beta. s i n Beta. s i n Beta. c o s Beta. ] \ begin {bmatrix} cosine beta & sin beta \ \ – sine beta & cosine beta \ \ \ end {bmatrix}

counterclockwise


[ c o s Beta. s i n Beta. s i n Beta. c o s Beta. ] \ begin {bmatrix} cosine beta & – sine beta \ \ sin beta & cosine beta \ \ \ end {bmatrix}

Code implementation

  1. Draw the hexagons. The first point is P1(0,50) and P2(50,50). The core module is placed into the hexagon method and iterated for 6 times.
var ctx = canvas.getContext("2d");

ctx.strikeStyle = "# 000";

ctx.beginPath();

const y = 50;

ctx.moveTo(0, y);

hexagon(ctx, 0, y, 50, y, 0.6);
Copy the code
  1. The input parameters of hexagon method are (CTX, x1, y1, x2, y2, n, m), which respectively represent the horizontal and vertical coordinates of points CTX, P1, and P2 in canvas context. N is the number of iterations, and m is the total number of iterations. The final code and effect are as follows.
function hexagon(ctx, x1, y1, x2, y2, n, m) {

    ctx.clearRect(0.0.300.300);

    // Clockwise 60 degrees

    ctx.moveTo(x2, y2);

    const x3 = x2 + (x2 - x1) * Math.cos(Math.PI / 3) + 
        (y2 - y1) * Math.sin(Math.PI / 3);

    const y3 = y2 - (x2 - x1) * Math.sin(Math.PI / 3) +
        (y2 - y1) * Math.cos(Math.PI / 3);

    ctx.lineTo(x3, y3);

    // Counterclockwise 120 degrees

    const x4 = x3 + (x3 - x2) * Math.cos((Math.PI * 2) / 3) -
        (y3 - y2) * Math.sin((Math.PI * 2) / 3);

    const y4 = y3 + (y3 - y2) * Math.cos((Math.PI * 2) / 3) +
        (x3 - x2) * Math.sin((Math.PI * 2) / 3);

    ctx.lineTo(x4, y4);

    ctx.stroke();

    n++;

    if (n === m) {
        return false;
    } else{ hexagon(ctx, x3, y3, x4, y4, n, m); }}Copy the code

Vector operations

It can be seen from the above process that point drawing by coordinate is very troublesome and time-consuming. In addition, Canvas 2D coordinates are upside down with the conventional data coordinate system, so we will spend a lot of time in the calculation of coordinate points.

Therefore, in order to keep the logic of the code simple and easy to read, we can change our thinking to understand coordinate points as vectors. Just to review the common vector operations are the following.

Vector addition/subtraction.The dot product, the cross product. As shown in Figure 1, vector addition means that the path is drawn along V1 and v2, and the coordinate point of P2 is the sum of two vectors v1 and V2, which is [x1+x2, y1+y2]. The dot product is the geometric meaning of the dot product of a and B vectors, and is the projection component of A vector times b vector on A vector, as shown in Figure 2. The cross product is the area of two vectors, as shown in Figure 3.

Code implementation

Vectors are described as arrays of the form [x,y].

class Vector extends Array {
    constructor(x = 1, y = 0) {
        super(x, y);
    }

    copy() {
        return new Vector(this.x, this.y); }}Copy the code

Vectors plus and minus.

add(v) {
    this.x += v.x;
    this.y += v.y;
    return this;
}

sub(v) {
    this.x -= v.x;
    this.y -= v.y;
    return this;
}
Copy the code

The vector cross dot dot.

cross(v) {
    return this.x * v.y - v.x * this.y;
}
dot(v) {
    return this.x * v.x + v.y * this.y;
}
Copy the code

Vector rotation.

rotate(rad) {
    const c = Math.cos(rad),
        s = Math.sin(rad);
    const [x, y] = this;
    this.x = x * c + y * -s;
    this.y = x * s + y * c;
    return this;
}
Copy the code

The code for drawing a hexagonal snowflake can be rewritten as follows by vector operations. First, the Canvas coordinate system is flipped up and down to form the coordinate mode we are used to.

var ctx = canvas.getContext("2d");
    ctx.translate(0, canvas.height);
    ctx.scale(1, -1);
Copy the code

Get v0 and v1 vectors, rotate them counterclockwise and clockwise, and get the same result as before, but the code is much more readable.

const v0 = new Vector(0.250);

const v1 = new Vector(50.250);

hexagon(ctx, v0, v1, 0.6);

function hexagon(ctx, v0, v1, n, m) {

    const v2 = v1.copy().sub(v0);
    v2.rotate((60 * Math.PI) / 180).add(v1); *// Counterclockwise 60 degrees *

    const v3 = v2.copy().sub(v1);
    v3.rotate((-120 * Math.PI) / 180).add(v2); *// Clockwise 120 degrees *ctx.beginPath(); ctx.moveTo(... v1); ctx.lineTo(... v2); ctx.lineTo(... v3); ctx.stroke(); n++;if (n === m) {
        return false;
    } else{ hexagon(ctx, v2, v3, n, m); }}Copy the code

practice

The logical law of snowflakes is actually a combination of recursion and basic graphics. In practice, we choose Koch snowflakes and hexagonal snowflakes to realize.

Koch snowflake

Koch snowflakes were first proposed by mathematician Helge von Koch and are one of the classic images in fractal geometry. Its generation is based on Koch curves, which are unilateral infinite fractals.

Take a look at the implementation first. Its base shape is an equilateral triangle. The area of the image is limited, but the perimeter is infinite. Each iteration increases the length of the line segment by one-third.

Train of thought

  1. Koch Snowflake is composed of Koch curves. Its basic shape is a triangle. Each side of the triangle is divided into three equal parts.
  2. Recursively perform step 1 in a recursive module until the set recursive level is reached.
Figure 4. Figure 5

Code implementation

The first thing is to do the coordinate transformation again, move the origin of the coordinate from the top left to the bottom left, and flip the Y-axis to up.

 var ctx = canvas.getContext("2d");
      ctx.translate(0, canvas.height);
      ctx.scale(1, -1);
Copy the code

Then we find the three vertices of the triangle. Assuming we start with v1(100,100) and the length of the triangle is 100, we can obtain the other two vertices, v2 and V3 vectors by vector transformation.

 var ctx = canvas.getContext("2d");

      const v1 = new Vector(100.100);
      const v2 = new Vector(100.0).add(v1);
      const v3 = new Vector(100.0).rotate((60 * Math.PI) / 180).add(v1);
Copy the code

Then we perform recursive operations on the three edges, namely v1v2, V2v3 and V3V2 vectors respectively.

      koch(ctx, v1, v2, 0, deep);
      koch(ctx, v2, v3, 0, deep);
      koch(ctx, v3, v1, 0, deep);
Copy the code

Function Koch (CTX, v1, v2, n, m) {},

This function takes five arguments: CTX is a Canvas2D context. V1 is the start vector of an edge, v2 is the end vector, n is the current iteration level, and m is the total number of iterations. In the module, according to the description in Figure 5, an edge is divided into four sections, each of which has the same length. You get v3, V4, v5. The termination condition is that the iteration level is the same as the specified number, and then the broken path of V1 ~ V5 is connected. This creates a Koch snowflake. Here’s the code.

function koch(ctx, v1, v2, n, m) {

    ctx.clearRect(0.0.300.300); *// Clear the artboard before each drawing

    const oneThirdVector = v2
        .copy()
        .sub(v1)
        .scale(1 / 3);

    const v3 = oneThirdVector.copy().add(v1);
    const v4 = oneThirdVector.copy().scale(2).add(v1);
    const v5 = v4.copy()
            .sub(v3)
            .rotate((-60 * Math.PI) / 180)
            .add(v3);

    n++;

    if (n === m) {

     // Stop recursion when the current level of the drawing matches the set levelctx.moveTo(... v1); ctx.lineTo(... v3); ctx.lineTo(... v5); ctx.lineTo(... v4); ctx.lineTo(... v2); ctx.stroke();return false;

    }

    // Recursively call drawing

    koch(ctx, v1, v3, n, m);
    koch(ctx, v3, v5, n, m);
    koch(ctx, v5, v4, n, m);
    koch(ctx, v4, v2, n, m);

}
Copy the code

Hexagonal snowflakes

Take a look at the implementation first. Its base shape is hexagonal. Each iteration takes 1/3 of the length of each line segment and draws the next small hexagon at the vertex of each corner. Repeat this step to get a hexagonal snowflake pattern.

# # #
  1. First of all, according to the formation rule of hexagonal snowflakes mentioned in the basic chapter, each two adjacent sides are a group, the first side turns 60 degrees left, the second side turns 120 degrees right, perform 6 times to get the basic shape of hexagonal snowflakes.
  2. Determine if the edge length is less than the specified minimum edge length before drawing the line. If not, change the length of the side to 1/3 of the original length and make a recursive call. If not, start drawing the hexagon.

Code implementation

Coordinate changes and getting the context of Canvas 2D are not covered here. First of all, let’s start from v0 and v1 and draw the pivot function of the hexagon.

      const v0 = new Vector(50.200);
      const v1 = new Vector(100.200);
      hexagon(ctx, v0, v1);
Copy the code

Function hexagon(CTX, v0, v1) {},

This function takes five arguments: CTX is a Canvas2D context. V0v1 is the starting vector, which gives us a starting direction for our drawing. In Hexagon, we calculated the hexagonal edge length hexagonLen of the iteration, and iterated when the termination condition was not met.

  function hexagon(ctx, v0, v1) {
    const hexagonLen = v1.copy().sub(v0).vlength;
    if (hexagonLen > minLine) {
        // Enter the iteration}}Copy the code

Here we divide the hexagon into six cycles. Each loop is from vstart to vmiddle to vend.

// Hexagon (CTX, v0, v1)
let vstart = v1.copy();
let vmiddle = v1.copy().sub(v0);
let vend = new Vector();
for (let i = 0; i < 6; i++) {
    // Hexagonal loop
}
Copy the code

In the body of the loop, we first get the vectors of vstart, vMIDDLE, vend in this iteration, and the next side length. If the new edge length is greater than the set minimum edge length minLine, the next iteration is done, otherwise, the three vectors are drawn. After drawing, update vstart and vmiddle.

  // Vector rotation

  vmiddle.rotate((60 * Math.PI) / 180).add(vstart);
  vend = vmiddle.copy().sub(vstart);
  vend.rotate((-120 * Math.PI) / 180).add(vmiddle);

  const thirdv = vend
          .copy()
          .sub(vmiddle)
          .scale(1 / 3)
          .add(vmiddle);

  const newHexagonLen = thirdv.copy().sub(vmiddle).vlength;

  if (newHexagonLen > minLine) {
      / / recursion
      hexagon(ctx, vmiddle, thirdv);
  } else {
      / / drawingctx.beginPath(); ctx.moveTo(... vstart); ctx.lineTo(... vmiddle); ctx.lineTo(... vend); ctx.stroke(); } vstart = vend.copy(); vmiddle = vend.copy().sub(vmiddle);Copy the code

In this way, we have achieved the method of drawing hexagonal snowflakes.

conclusion

In the process of drawing snowflakes, we reviewed the following knowledge points: First of all, we can meet the actual needs through coordinate transformation in Canvas, and reasonable coordinate changes can make our drawing logic easy to understand. Secondly, vector is a very convenient and basic tool in visualization. To master and learn to use vector thinking calculation is the only way to learn visualization. Finally, most of the fractal graphs represented by snowflake patterns are realized by means of meta-image plus iteration. Practicing the drawing of fractal graphs helps us to master various recursive operations and summarize the logical methods of such graphs.

reference

  1. Canvas generates Koch snowflake (curve)
  2. GitHub – akira- CN/Graphics: Some small examples related to graphics systems
  3. Two dimensional rotation matrix and vector rotation
  4. Code: Canvas Fenxing – CodeSandbox