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

WebGL Lesson 31: Simulating nibbles with Multiple Lattice ObjectsCopy the code

Helpful hints

This article is the 31st in the WebGL Curriculum column, and it is highly 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 31 Code

primers

In the previous article, we could use the Uniform variable in the Shader to make different Settings for different drawn objects, resulting in a single effect. For example, color.

In fact, stretch, displacement, rotation, etc. can all be set using uniform variables. Instead of regenerating and passing buffer data. This is good because it saves performance.

In this article, first achieve stretch, displacement, rotation uniform Settings, and then use a number of lattice objects to simulate the digital circuit in the digital tube.

The effect is as follows:

There are seven grids, each of which can be controlled independently. Let’s arrange these seven cells in the form of a digital tube.

Start at the bottom cell and go counterclockwise. The last cell is the middle cell.

Initialization of seven cells

In our previous two articles, we used two parameters: width and height. Here, we switch the two concepts:

Scalex and scaley

It’s stretching in the x direction and stretching in the y direction.

When we generate buffer data for the grid, we also do not depend on the width and height. Let’s fix it:

The bottom left corner of the grid is the bottom left corner of the screen. The bottom right corner of the grid is the bottom right corner of the screen. The top left corner of the grid is the top left corner of the screen. The top right corner of the grid is the top right corner of the screen.

The code for generating grid data is as follows:

        this.data = [
            // The first triangle
            -1, -1.// Lower left corner dot
            1, -1.// Lower right corner dot
            1.1.// Dot in the upper right corner
            // The second triangle
            1.1.// Dot in the upper right corner
            -1.1.// top left point
            -1, -1.// Lower left corner dot
        ];
        this.dataArr = new Float32Array(this.data);
Copy the code

To make the grid flat or long, we can use Scalex and Scaley.

Give the updated constructor first:


class GridObject {
    // Width height x coordinates y coordinates
    // The central point is the reference point
    constructor(scalex, scaley, posx, posy) {
        this.scalex = scalex;
        this.scaley = scaley;
        this.posx = posx;
        this.posy = posy;
        this.modelUpdated = false; // Whether the model is updated, that is, whether it needs to be redrawn
        this.glbuffer = null;
        this.a_PointVertex = null;
        this.color = { R: 0.G: 0.B: 0 };
        this.rotate = 0; }... }Copy the code

Ok, so when we initialize, we just new seven GridObject:

function generateDigitGrid(gl) {
    //////////////////////////
    let idx = 0;
    for (; idx <= 6; idx++) {
        let digit0 = new GridObject(0.3.0.1.0.0.0); // Compress to 0.3 in x direction and 0.1 in y direction
        gridList.push(digit0);
    }
    gridList.forEach(element= > {
        element.genData(gl);
    });
}
Copy the code

So the seven initialized cells, all together, are horizontal and flat.

With rotate, you can rotate some of these cells by 90 degrees to make them vertical.

Use HTML page controls to modify the rotation and position of the grid

We used a global variable gridList above to store objects with seven grids.

Since our goal is to make changes for each cell, we need to add a drop-down list space to select the target:

    <p>
        <b>Pick the number of cells</b>
        <select id="chosen" oninput="chosen_selected()">
            <option value="none">none</option>
            <option value="0">0</option>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
            <option value="5">5</option>
            <option value="6">6</option>
        </select>
    </p>
Copy the code

As shown in the code above, 0-6, seven cells, very reasonable.

Then we need to modify the parameters, all with the control out:

< p > < b > grid x direction tensile: < / b > < input id = "gridscalex" type = "range" min = "0.1" Max = "1" value = "1" step = "0.01" the oninput = "gl_draw ()" / > 0 < b id = "gridscalexvalue" > < / b > < / p > < p > < b > y direction of the lattice stretch: < / b > < input id = "gridscaley" type = "range" min = "0.1" Max = "1" value = "1" Step = "0.01" the oninput = "gl_draw ()" / > < b id = "gridscaleyvalue" > 0 < / b > < / p > < p > < b > grid x direction displacement: < / b > < input id = "gridposx" Type = "range" min = "1" Max = "1" value = "0" step = "0.01" the oninput = "gl_draw ()" / > < b id = "gridposxvalue" > 0 < / b > < / p > < p > The y direction of < b > grid displacement: < / b > < input id = "gridposy" type = "range" Max min = "1" = "1" value = "0" step = "0.01" the oninput = "gl_draw ()" / > < b Id = "gridposyvalue" > 0 < / b > < / p > < p > < b > lattice rotation (radians) : < / b > < input id = "gridrotate" type = "range" min = "0" Max = "360" value = "0" step="10" oninput="gl_draw()" /> <b id="gridrotatevalue">0</b> </p>Copy the code

Our logic is that if a target is selected, the parameter controls above should first display the current state of the selected GridObject. So the selected callback function chosen_selected:

function chosen_selected() {
    console.log(chosenDom.value);
    if (chosenDom.value === "none") {
        return;
    }
    //////////////////////////////////
    gridscalexDom.value = gridList[chosenDom.value].scalex;
    gridscaleyDom.value = gridList[chosenDom.value].scaley;
    gridposxDom.value = gridList[chosenDom.value].posx;
    gridposyDom.value = gridList[chosenDom.value].posy;

    gridrotateDom.value = gridList[chosenDom.value].rotate;

    gridscalexvalueDom.innerText = gridscalexDom.value;
    gridscaleyvalueDom.innerText = gridscaleyDom.value;

    gridposxvalueDom.innerText = gridposxDom.value;
    gridposyvalueDom.innerText = gridposyDom.value;

    gridrotatevalueDom.innerText = gridrotateDom.value;
}
Copy the code

As shown above, after we select a target cell, we can immediately view the current state through the various parameter controls. Here UI control code is really very long and difficult to write, there is no two-way binding what, write, is really more trouble.

Then it is time to modify the specific parameters. Let’s observe the rotation control’s callback function:

< p > < b > lattice rotation (perspective) : < / b > < input id = "gridrotate" type = "range" min = "0" Max = "360" value = "0" step = "10" the oninput = "gl_draw ()" / > <b id="gridrotatevalue">0</b> </p>Copy the code

Let’s look at the gl_draw function implementation:

function gl_draw() { gridscalexvalueDom.innerText = gridscalexDom.value; gridscaleyvalueDom.innerText = gridscaleyDom.value; gridposxvalueDom.innerText = gridposxDom.value; gridposyvalueDom.innerText = gridposyDom.value; gridrotatevalueDom.innerText = gridrotateDom.value; gl.enable(gl.CULL_FACE); gl.enable(gl.DEPTH_TEST); gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); if (chosenDom.value ! == "none") { gridList[chosenDom.value].scalex = gridscalexDom.value; gridList[chosenDom.value].scaley = gridscaleyDom.value; gridList[chosenDom.value].posx = gridposxDom.value; gridList[chosenDom.value].posy = gridposyDom.value; gridList[chosenDom.value].rotate = gridrotateDom.value; } gridList.forEach(element => { element.render(gl, program); }); }Copy the code

As we can see, the first is the display of the control itself.

This is followed by a series of webGL cleanup codes that clean out the current display for redrawing.

We then check if the target is None, and if not, update the selected parameter in the global variable gridList to the value in the current control.

And finally, just a normal traversal, drawing.

It is important to mention that the render function of the grid is called:

render(gl, program) {
        gl.bindBuffer(gl.ARRAY_BUFFER, this.glbuffer);
        if (this.modelUpdated) {
            this.modelUpdated = false;
            if (this.a_PointVertex == null) {
                this.a_PointVertex = gl.getAttribLocation(program, 'a_PointVertex'); }}//////////////////////////
        gl.vertexAttribPointer(this.a_PointVertex, 2, gl.FLOAT, false.0.0);
        gl.enableVertexAttribArray(this.a_PointVertex);
        gl.uniform3f(u_color, 1 - this.color.R, 1 - this.color.R, 1 - this.color.R);
        //
        let rad = ((2 * Math.PI) / 360) * this.rotate;
        gl.uniformMatrix3fv(u_all_loc, false, genMat3ForGL(this.scalex,
            this.scaley, rad, this.posx, this.posy
        ));
        gl.drawArrays(gl.TRIANGLES, 0.this.pointCount);
    }
Copy the code

We can see the color part, we only used the R component, because the nixie tube doesn’t have to be too fancy. 例 句 : I don’t mind you being a little flashy.

And then look at how the Angle is converted into radians, and then most importantly how the stretch, rotation, and displacement are softened into a matrix, and then pass the value of the matrix to the uniform variable U_ALL_LOc. If you’re interested in this, you can go back to the previous article, where you spent a lot of time talking about this matrix problem.

One goal that has been accomplished above is to place different grids that look like a niketube……

Next comes the display of the nixie tube

Nixie tube display, in fact, different grid, on and off.

For example, the number 0, when displayed on a nixie tube, only the middle one is not lit, and all the others are lit.

Or the number 1, for example, is the number that lights up the last two squares and doesn’t light up the rest.

By analogy, we can get the on and off situation of the corresponding grid of 9 digits, which can be written in the following code:

// Determine the brightness of each bit of the nixie tube according to the number to be displayed
function digitEncode(digit) {
    if (digit == 0) {
        gridList[0].color.R = 1;
        gridList[1].color.R = 1;
        gridList[2].color.R = 1;
        gridList[3].color.R = 1;
        gridList[4].color.R = 1;
        gridList[5].color.R = 1;
        gridList[6].color.R = 0;
        return;
    }
    if (digit == 1) {
        gridList[0].color.R = 0;
        gridList[1].color.R = 1;
        gridList[2].color.R = 1;
        gridList[3].color.R = 0;
        gridList[4].color.R = 0;
        gridList[5].color.R = 0;
        gridList[6].color.R = 0;
        return;
    }
    // The reader can draw the rest of the result by himself.. }Copy the code

We just need to call the above digitEncode function before the final drawing of gl_draw function, pass in the corresponding number, you can make the 7 squares work correctly, display the corresponding number:

function gl_draw() {... digitEncode(digitDom.value);// Drop down the value of the control

    gridList.forEach(element= > {
        element.render(gl, program);
    });
}
Copy the code

The code for the pull-down controls is omitted, and the reader can fill it in.

To this, already can, select a number, you can let the digital tube display!! Take a look at the graph below:

Kind of fun, I have to say!

Get some work done. Get a timer

I don’t even bother to write it in code, just go to the browser Console:

Thanks to my good code design, global variables are fun to use, so I’ll console it with two sentences:

let counter = 0;
setInterval(function(){ counter ++; digitDom.value = counter%10; gl_draw(); },1000);
Copy the code

This code goes directly to the browser console and hits Enter:

Extend this code to make a hanging clock on the page.




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

Small melon melon said: I remember the digital circuit seems to learn this, now forget.

  • You should review it if you forget it, right? Let’s do it.