This is the sixth day of my participation in the Novembermore Challenge.The final text challenge in 2021

WebGL Lesson 34:2D Jigsaw PuzzlesCopy the code

Helpful hints

This article is the 34th in the WebGL Curriculum column, and it is strongly recommended to start with the previous one. Because we spent a lot of time talking about vectors and matrix operations. These basics will influence your thinking.

The code of this lesson can be directly jumped to:Lesson 34 Code

primers

The main purpose of this article is to show how to display images with multiple scattered objects.

Our code abstracts a GridObject class that, if rendered, contains six vertices and two triangles forming a rectangular shape. We can set the 6-vertex UV so that it can display images.

In the previous articles, the normal UV Settings were used so that the vertices of the grid and the image fit perfectly, so that a grid displays a complete image.

But sometimes, we need to split a whole image, for example, into nine, nine display images.

The effect is as follows:

Effect analysis

The above effect, to be honest, there are many ways to achieve. But it’s better to use nine different grids to anchor different UVs. This gives you more freedom of control. For example, one of the cells can be rotated:

Details of the implementation

Let’s analyze the lower-left box, the one with half the cat’s claws:

Anchor this grid with the full picture:

The bottom left is anchored to the bottom left of the full image, which is UV = [0,0].

Position 1/3 of the complete image anchored in the upper right corner, UV = [1/3, 1/3]

Now let’s look at the position of this grid: in the Canvas coordinate system, the position of this grid should be [-2/3, -2/3].

In this way, the UV and position of nine grids can be obtained.

Let’s change the GridObject code to support UV modification:

    constructor(scalex, scaley, posx, posy){... .// add a new field, uv
        this.uv = { leftbottom: [0.0].topright: [1.1]}; }genData(gl) {
        this.data = [
            // The first triangle
            -1, -1.this.uv.leftbottom[0].this.uv.leftbottom[1].// Lower left corner dot
            1, -1.this.uv.topright[0].this.uv.leftbottom[1].// Lower right corner dot
            1.1.this.uv.topright[0].this.uv.topright[1].// Dot in the upper right corner
            // The second triangle
            1.1.this.uv.topright[0].this.uv.topright[1].// Dot in the upper right corner
            -1.1.this.uv.leftbottom[0].this.uv.topright[1].// top left point
            -1, -1.this.uv.leftbottom[0].this.uv.leftbottom[1].// Lower left corner dot]; . . . }Copy the code

Now, after modifying the code, the UV information for GridObject can be changed at any time.

Initialize nine cells

Based on the analysis and changes to GridObject described in the previous section, we can write the nine grid generation code correctly at initialization:

function generateGrid(gl) {
    //////////////////////////
    let level = 0; // The bottom of the nine cells is the 0 layer, the middle layer is 1, and the top layer is 2
    let tempGrid;
    let xScale = 0.3;
    let yScale = 0.3;
    for (; level <= 2; level++) {
        tempGrid = new GridObject(xScale, yScale, -2 / 3, -2 / 3 + level * (2 / 3));
        tempGrid.uv.leftbottom[1] = (1 / 3) * level;
        tempGrid.uv.topright[0] = 1 / 3;
        tempGrid.uv.topright[1] = 1 / 3 + (1 / 3) * level;
        gridList.push(tempGrid);

        tempGrid = new GridObject(xScale, yScale, 0, -2 / 3 + level * (2 / 3));
        tempGrid.uv.leftbottom[0] = 1 / 3;
        tempGrid.uv.leftbottom[1] = (1 / 3) * level;
        tempGrid.uv.topright[0] = 2 / 3;
        tempGrid.uv.topright[1] = 1 / 3 + (1 / 3) * level
        gridList.push(tempGrid);
        
        tempGrid = new GridObject(xScale, yScale, 2 / 3, -2 / 3 + level * (2 / 3));
        tempGrid.uv.leftbottom[0] = 2 / 3;
        tempGrid.uv.leftbottom[1] = (1 / 3) * level;
        tempGrid.uv.topright[0] = 3 / 3;
        tempGrid.uv.topright[1] = 1 / 3 + (1 / 3) * level;
        gridList.push(tempGrid);

    }

    gridList.forEach(element= > {
        element.genData(gl);
    });
}

Copy the code

Note that the xScale and yScale of the code above are both 0.3.

Why is that? Some people prefer to add a small boundary. If you don’t want such a large boundary, you can change the above two values to 1/3-0.01. The effect is as follows:

The closer you get to 1/3, the smaller the gap in the middle, depending on your personal preference.

Jigsaw puzzle

To implement a jigsaw puzzle, we remove the upper right corner of the grid, because that’s how jigsaw puzzles are defined, one grid must be missing:

And then logically, we can control the movement of a cell.

This is actually implemented with GridObject, so I’m going to change it

posx
posy
Copy the code

Let’s experiment by moving the tail grid to the upper right corner, the seventh grid, and setting it to:

gridList[7].posx = 2/3;
gridList[7].posy = 2/3;
Copy the code

The effect is as follows:

Yhoo, that’s funny!

Limits on cell movement

For jigsaw puzzles, at most three squares can move in any given state, and they move in different directions.

So, I press WASD on the keyboard to control, and I can get down to the grid.

For example, by default, if I press W, it must be the middle row, and the one on the far right moves up one grid.

According to this idea, when we press W on the keyboard,

  • The first step is to find which cell is empty
  • And then I’m going to look underneath this box and see if there’s a box
  • If there is a grid, move the grid onto it
  • If there is no grid, do nothing and ignore the keyboard events

To find which cells are empty, we need an extra array to keep track of the occupied cells:

var NineGrid = [-1, -1, -1, -1, -1, -1, -1, -1, -1];
Copy the code

The subscript represents the position of the nine grids, with 0 being the lower left and 8 being the upper right. The value that’s stored in it, is the subscript of the gridList, which small image it is.

After initialization, NineGrid should be:

[0, 1, 2, 3, 4, 5, 6, 7, -1]
Copy the code

This means that the upper right corner of the grid is not occupied, the rest of the grid is occupied by a certain grid.

With this NineGrid array, it’s easy to find Spaces, just iterate, and find -1.

The next step is to find the top, bottom, and left of a certain grid. What grid is it? You can look at the picture and write it directly:

var NineGridNeigbour = [
    [3, -1, -1.1].// box 0 is 3, invalid, invalid, 1
    [4, -1.0.2].// Box 1 is left and right
    [5, -1.1, -1].// Box 2, left and right
    [6.0, -1.4].// Box 3, up and down
    [7.1.3.5].// Box 4, up and down
    [8.2.4, -1].// Box 5, left and right
    [-1.3, -1.7].// Box 6, up and down
    [-1.4.6.8].// Box 7, left, right, left, right
    [-1.5.7, -1].// Box 8, up and down
];
// cur: current subscript
// dir: 0, 1, 2, 3 respectively represent up, down, left, and right
// Find the corresponding grid through a grid and the direction
function NineGridFind(cur, dir) {
    return NineGridNeigbour[cur, dir];
}

Copy the code

As can be seen from the code, if the return value found is -1, it indicates that a certain direction of the current grid is already a boundary, meaningless.

Keyboard events, and subsequent logic

Listening for keyboard events is very easy:

window.onkeyup = function (event) {
    console.log(event.keyCode);
};
Copy the code

Event. keyCode is a number that can be easily tested:

  • w: 87
  • s: 83
  • a: 65
  • d: 68

Let’s fill in the logic of the event function, first to show which cell should move, and let’s rotate the cell by 45 degrees:

    window.onkeyup = function (event) {
        console.log(event.keyCode);
        let emptyIdx = NineGridEmpty(); // Find the space bar
        let target = -1;
        if (event.keyCode == 87) { // Press w on the keyboard
            target = NineGridFind(emptyIdx, 1);// 1 means to find the following
        } else if (event.keyCode == 83) { // s
            target = NineGridFind(emptyIdx, 0);// 0 means to find the top
        } else if (event.keyCode == 65) { // a
            target = NineGridFind(emptyIdx, 3);// 3 means to find the one on the right
        } else if (event.keyCode == 68) { // d
            target = NineGridFind(emptyIdx, 2);// 2 means to find the one on the left
        }
        console.log('target', target, 'emptyIdx', emptyIdx);
        if (target == -1) { // The boundary is meaningless
            return;
        }
        let targetGrid = gridList[target];
        targetGrid.rotate = 45;
        gl_draw();
    };
Copy the code

Ok, we hit W on the keyboard and it says:

Try it again. Press the D key.

Yeah, that’s right. I found the cell I need to move.

The next step is to simply move the target grid. After moving, don’t forget to set NineGrid to update the usage of the NineGrid.

The code is as follows:

    window.onkeyup = function (event) {
      console.log(event.keyCode);
      let emptyIdx = NineGridEmpty(); // Find the space bar
      let target = -1;
      let posxMove = 0;
      let posyMove = 0;
      if (event.keyCode == 87) { // Press w on the keyboard
          target = NineGridFind(emptyIdx, 1);// 1 means to find the following
          posyMove = 2 / 3;
      } else if (event.keyCode == 83) { // s
          target = NineGridFind(emptyIdx, 0);// 0 means to find the top
          posyMove = -2 / 3;
      } else if (event.keyCode == 65) { // a
          target = NineGridFind(emptyIdx, 3);// 3 means to find the one on the right
          posxMove = -2 / 3;
      } else if (event.keyCode == 68) { // d
          target = NineGridFind(emptyIdx, 2);// 2 means to find the one on the left
          posxMove = 2 / 3;
      }
      console.log('target', target, 'emptyIdx', emptyIdx);
      if (target == -1) { // The boundary is meaningless
          return;
      }
      let targetGrid = gridList[NineGrid[target]];
      // targetGrid.rotate += 15;
      targetGrid.posx += posxMove;
      targetGrid.posy += posyMove;
      NineGrid[emptyIdx] = NineGrid[target];
      NineGrid[target] = -1;
      gl_draw();
  };

Copy the code

Ok, here so far, you can casually press WSAD to move the grid, first upset, and then put together, see you can not!!




With the end of the text, here is the q&ACopy the code

Xiao Neng can say: what if I want to press the keyboard and there is a move gradient instead of a stiff move?

  • Then gradually change the position of the target grid, there are many ways to do this, not to mention here.