Take only one drawcall!

The effect

implementation

The overall train of thought

Follow these steps

  • Initialize a polygon.
  • Folded and split into two polygons.
  • If you need to continue splitting, fold all the polygons in the field, folding the new polygon to the opposite level of the original.

So, all calculations and renderings can be translated into a polygon operation.

To simplify the calculation, we agree to initialize polygons as convex polygons. This has several advantages.

  • The result is still a convex polygon, and only two are folded for each polygon
  • It is especially convenient to divide convex polygons into triangles when rendering, i.e. the vertex index can be calculated quickly

To calculate

It is mainly divided into three parts

  • Polygon division
  • Lines intersects the
  • axisymmetric

segmentation

Observe the relationship between the touch direction and the points of the polygon.

It can be found that the Angle between the touch direction and the point pointing to the vertex of the polygon determines the point of the divided polygon.

  • When the Angle is greater than 90 degrees, the vertex is exactly the vertex of the folded polygon.
  • When the Angle is 90 degrees, the vertex is the vertex of two polygons.
  • When the Angle is less than 90 degrees, the vertex is the vertex of the bottom polygon.

The dot product between vectors is a good way to figure out the Angle problem.

const dotValue = temp_v2_vector.dot(temp_v2_vector_3)
if (Math.abs(dotValue) === 0) {
    // Just at the point
} else if (dotValue > 0) {
    / / in the front
} else {
    / / in the back
}
Copy the code

The intersection point

When the Angle between the two adjacent points of the divided polygon and the touch direction is different (the points belonging to two polygons), the intersection of the vertical line of the touch vector and the line segment needs to be calculated.

A point on a line can be represented by a point and a vector.

Combine the point expressions of the two lines and use Cramer’s Rule to find the point of intersection.

function linelinePoint(p1: Vec2, p1Dir: Vec2, p2: Vec2, p2Dir: Vec2) {
    const a1 = p1Dir.x, b1 = -p2Dir.x, c1 = p2.x - p1.x
    const a2 = p1Dir.y, b2 = -p2Dir.y, c2 = p2.y - p1.y
    const d = a1 * b2 - a2 * b1,
        d1 = c1 * b2 - c2 * b1,
        d2 = a1 * c2 - c1 * a2
    const t1 = d1 / d, t2 = d2 / d
    return [t1, t2]
}
Copy the code

This computes the scale T, which can be used not only to figure out the vertex coordinates, but also to figure out the intersecting texture coordinates.

const posSpilt = Vec2(pos.x + dir.x * t1, pos.y + dir.y * t1)
const uvSpilt = Vec2(uv.x + uvdir.x * t1, uv.y + uvdir.y * t1)
Copy the code

Symmetric point

The vertices of the folded polygon are exactly the points of vertical axisymmetric contact of the vertices of the original polygon.

Finding points of symmetry can also be done by vector calculation.

  1. Find the vector between the vertex and the midpoint
  2. Find the projection (dot product) of the unit vector of the point in the direction of the touch, which is exactly half the distance
  3. Find the coordinates of the symmetric points (distance times direction vector + starting point)

Vec2.subtract(temp_v2_vector_4, temp_v2_pos, pos)
const dotLength = temp_v2_vector_4.dot(temp_v2_vector) * 2
temp_v2_pos_2.set((pos.x + temp_v2_vector.x * dotLength), pos.y + temp_v2_vector.y * dotLength)
Copy the code

Apply colours to a drawing

Rendering a graph is usually made up of triangles.

For convex polygons, splitting triangles is easier. By taking one vertex and connecting it to the others, you can divide a polygon into triangles.

Rendering a convex polygon uses Assembler method to assemble vertex data.

It is divided into the following steps:

  1. Put theSprite-simpleThe assembler is copied out as its own assembler template.
  2. Create a new class inheritanceSpriteAnd sets its assembler to its own assembler
  3. Create variable vertex array, texture array.
  4. Write the assembler logic

Let’s look at the code 😀

Class of convex polygons.

// Only convex polygons
@ccclass('PolygonSprite')
export class PolygonSprite extends Sprite {
    @property({ type: [Vec2] })
    protected _vertices: Vec2[] = [new Vec2(-100, -100), new Vec2(100, -100), new Vec2(100.100), new Vec2(-100.100)];
    // Omit some code

    @property({ type: [Vec2] })
    protected _uvs: Vec2[] = [new Vec2(0.0), new Vec2(1.0), new Vec2(1.1), new Vec2(0.1)];
    // Omit some code

    protected _flushAssembler() {
        // Point to a custom assembler
        let assembler = polygonAssembler;
        if (this._assembler ! == assembler) {this.destroyRenderData();
            this._assembler = assembler;
        }
        // Omit some code}}Copy the code

Next, look at the modifications within the assembler.

Processing vertex data

// Save vertex data
updateVertexData(sprite: PolygonSprite) {
    // Intermediate variable
    const renderData = sprite.renderData;
    if(! renderData) {return;
    }
    renderData.vertexCount = renderData.dataLength = sprite.vertices.length
    // Number of triangles = number of vertices - 2
    // Number of indexes = number of triangles X3
    renderData.indicesCount = (renderData.vertexCount - 2) * 3
    renderData.vertDirty = false;
    for (let i = 0; i < sprite.vertices.length; ++i) {
        const xy = sprite.vertices[i];
        renderData.data[i].x = xy.x
        renderData.data[i].y = xy.y
    }
},
Copy the code

Cache UV coordinates. Texture coordinates defined by us are normalized to 0-1, and then converted according to the actual texture coordinates (combined picture) when updating data.

updateUvs(sprite: PolygonSprite) {
    constrenderData = sprite.renderData! ;//实际uv
    constuv = sprite.spriteFrame! .uv;// Bottom left, top right
    const l = uv[0], b = uv[1], t = uv[7], r = uv[6]
    for (let i = 0; i < sprite.uvs.length; ++i) {
        const uvs = sprite.uvs[i];
        renderData.data[i].u = l + (r - l) * uvs.x
        renderData.data[i].v = b + (t - b) * uvs.y
    }
    renderData.uvDirty = false;
},
Copy the code

The fill data is modified so that the vertex index is connected to the triangle of each vertex starting from the first point.

fillBuffers(sprite: PolygonSprite, renderer: any) {
    // omit the code

    // Fill vertices
    for (let i = 0; i < renderData.vertexCount; ++i) {
        const vert = renderData.data[i];
        // Calculate the world coordinatesvBuf! [vertexOffset++] = a * vert.x + c * vert.y + tx; vBuf! [vertexOffset++] = b * vert.x + d * vert.y + ty; vBuf! [vertexOffset++] = vert.z;/ / fill the uvvBuf! [vertexOffset++] = vert.u; vBuf! [vertexOffset++] = vert.v; Color.toArray(vBuf! , sprite.color, vertexOffset); vertexOffset +=4;
    }

    // Populate the index
    for (let i = 0; i < sprite.vertices.length - 2; ++i) {
        conststart = i; iBuf! [indicesOffset++] = vertexId; iBuf! [indicesOffset++] = start +1+ vertexId; iBuf! [indicesOffset++] = start +2+ vertexId; }},Copy the code

summary

Implementing the folding effect can break down the problem into processing a polygon and implement batch rendering using Assembler.

Use Cocos Creator 3.0.0 to achieve “Origami effect!” Technology sharing. Welcome three consecutive (like/read/leave a comment/share) support!

keep hungry! keep foolish!

Full Project Link – Cocos Store (100 points)

Of course, you can also find the address of all the tutorial projects in the url 😀 original link