How to draw a continuous mesh wall in triangles? So it isn't missing half the triangles?

I am trying to do programmatic mesh generation, where i provide a parallel source and target list of points and then mesh between them (like to make a wall, where floor perimeter is source, and target is ceiling perimeter, equal number of points for both).

For example, I am testing with less than 50 verts such as:

source = [(0,0), (1,0), (2,0)]
target = [(0,1), (1,1), (2,1)]

I can do this via β€˜triangle soup’ - where I make each triangle completely separately:

static getGeometryFilledQuadsSharp(
    originV3: Vector3[],
    targetV3: Vector3[],
    originUV: Vector2[], // full ref uv at source points
    targetUV: Vector2[], // full ref uv at target points
    ccw = true) {


    let geometry = new BufferGeometry();

    //━━━━━━━━━━━━━━━━━━━━━━━━━━━
    // DEFINE BUFFERS
    //━━━━━━━━━━━━━━━━━━━━━━━━━━━
    let posArray = new Float32Array(originV3.length * 18); // 6 verts x3 (x,y,z)
    let uvArray = new Float32Array(originV3.length * 12); // 6 verts Γ— 2 (u,v)

    
    //━━━━━━━━━━━━━━━━━━━━━━━━━━━
    // MAIN LOOP
    //━━━━━━━━━━━━━━━━━━━━━━━━━━━
    for (let i = 0; i < originV3.length; i++) {

        let iPlusOne = i + 1;
        if (iPlusOne > originV3.length - 1) iPlusOne = 0;

        let v1Index = iPlusOne;

        let k = i * 18;

        //━━━━━━━━━━━━━━━━━━━━━━━━━━━
        // TRIANGLE POSITIONS 
        //━━━━━━━━━━━━━━━━━━━━━━━━━━━
        // triangle A: (origin[i], target[v1Index], origin[v1Index])
        posArray[k] = originV3[i].x; k++;
        posArray[k] = originV3[i].y; k++;
        posArray[k] = originV3[i].z; k++;

        posArray[k] = targetV3[v1Index].x; k++;
        posArray[k] = targetV3[v1Index].y; k++;
        posArray[k] = targetV3[v1Index].z; k++;

        posArray[k] = originV3[v1Index].x; k++;
        posArray[k] = originV3[v1Index].y; k++;
        posArray[k] = originV3[v1Index].z; k++;

        // triangle B: (origin[i], target[v1Index], origin[v1Index])
        posArray[k] = targetV3[i].x; k++;
        posArray[k] = targetV3[i].y; k++;
        posArray[k] = targetV3[i].z; k++;

        posArray[k] = targetV3[v1Index].x; k++;
        posArray[k] = targetV3[v1Index].y; k++;
        posArray[k] = targetV3[v1Index].z; k++;

        posArray[k] = originV3[i].x; k++;
        posArray[k] = originV3[i].y; k++;
        posArray[k] = originV3[i].z; k++;

        //━━━━━━━━━━━━━━━━━━━━━━━━━━━
        // UVs
        //━━━━━━━━━━━━━━━━━━━━━━━━━━━
        let u = i * 12; // 6 points * 2

        // allocate uv across 6 vertices 

        // Triangle A: (origin[i], target[v1Index], origin[v1Index])
        uvArray[u++] = originUV[i].x;
        uvArray[u++] = originUV[i].y;

        uvArray[u++] = targetUV[v1Index].x;
        uvArray[u++] = targetUV[v1Index].y;

        uvArray[u++] = originUV[v1Index].x;
        uvArray[u++] = originUV[v1Index].y;

        // Triangle B: (target[i], target[v1Index], origin[i])
        uvArray[u++] = targetUV[i].x;
        uvArray[u++] = targetUV[i].y;

        uvArray[u++] = targetUV[v1Index].x;
        uvArray[u++] = targetUV[v1Index].y;

        uvArray[u++] = originUV[i].x;
        uvArray[u++] = originUV[i].y;

    }

    //━━━━━━━━━━━━━━━━━━━━━━━━━━━
    // SET RESULTS
    //━━━━━━━━━━━━━━━━━━━━━━━━━━━
    geometry.setAttribute('position', new BufferAttribute(posArray, 3));
    geometry.setAttribute('uv', new BufferAttribute(uvArray, 2));

    // merge it manually then
    //geometry = BufferGeometryUtils.mergeVertices(geometry);

    // return
    geometry.computeVertexNormals();
    return geometry;
}


This works fine. Because every triangle is distinct, the wall is fully visible. But as soon as I merge the vertexes (so I can get true smoothing) it destroys half the triangles. So they are not merging properly.

I have tried also drawing them from scratch via indexed verts but this doesn’t work either - only half the triangles at most are visible (even with two sided material, or mesh material):

static getGeometryFilledQuadsSmooth(
originV3: Vector3[],
targetV3: Vector3[],
originUV: Vector2[],
targetUV: Vector2[],
ccw = true
): BufferGeometry {

const g = new BufferGeometry();
const N = originV3.length;

const pos = new Float32Array(N * 2 * 3);
const uv = new Float32Array(N * 2 * 2);
const idx = new Uint16Array(N * 6);


// vertices: [origin ring, target ring, origin ...],
for (let i = 0; i < N; i++) {

    // interleave, so order of points is:
    // => origin_0x3, target_0x3, origin_1x3, target_1x3, etc..
    const oVert = 2 * i;
    const tVert = 2 * i + 1;

    // origin pos
    let o3 = oVert * 3;
    pos[o3 + 0] = originV3[i].x;
    pos[o3 + 1] = originV3[i].y;
    pos[o3 + 2] = originV3[i].z;

    // target pos
    let t3 = tVert * 3;
    pos[t3 + 0] = targetV3[i].x;
    pos[t3 + 1] = targetV3[i].y;
    pos[t3 + 2] = targetV3[i].z;

    // origin uv
    let o2 = oVert * 2;
    uv[o2 + 0] = originUV[i].x;
    uv[o2 + 1] = originUV[i].y;

    // target uv
    let t2 = tVert * 2;
    uv[t2 + 0] = targetUV[i].x;
    uv[t2 + 1] = targetUV[i].y;

}

// DRAW FROM INDICES: connect i -> j (next) between rings
for (let i = 0; i < N; i++) {
    const j = (i + 1) % N;

    const origin_i = 2 * i;
    const target_i = 2 * i + 1;

    const origin_j = 2 * j;
    const target_j = 2 * j + 1;

    const k = i * 6;

    if (ccw) {
        // tri A: (origin_i, origin_j, target_j)
        idx[k + 0] = origin_i;
        idx[k + 1] = origin_j;
        idx[k + 2] = target_j;

        // tri B: (origin_i, target_j, target_i)
        idx[k + 3] = origin_i;
        idx[k + 4] = target_j;
        idx[k + 5] = target_i;

    } else {
        // flipped winding
        idx[k + 0] = origin_i;
        idx[k + 1] = target_j;
        idx[k + 2] = origin_j;

        idx[k + 3] = origin_i;
        idx[k + 4] = target_i;
        idx[k + 5] = target_j;

    }
}


g.setAttribute("position", new BufferAttribute(pos, 3));
g.setAttribute("uv", new BufferAttribute(uv, 2));
g.setIndex(new Uint16BufferAttribute(idx, 1));

g.computeVertexNormals();
g.computeBoundingSphere();
g.computeBoundingBox();

return g;
}




I cannot figure out what the trick is. I asked ChatGPT and it cannot tell me what is wrong here.

I assume there’s some magic way to draw a wall or fill these quads in ThreeJs but I can’t find it.

Any help? Thanks.

I don’t know exactly what you want to achieve overall.
Perhaps you can take some inspiration from the design of my showroom?

see also

wall:

SHOWROOM/showroom/showroomBuildt.js at aa7a082a1b7dacd17e1d075f95215d2858212b67 Β· hofk/SHOWROOM Β· GitHub

line 239 function buildWall( i )

If half of the triangles are missing, my first immediate guess would be the winding order. You create triangles in pairs, check whether they have the same windings.

2 Likes

When you say β€œmerge vertices” do you mean BufferGeometryUtils.mergeVertices?

I think that’s the easiest way to marge vertices but only works if you don’t have other attributes that prevent merging.. like unique UVs or face oriented normals.

If you do have unique uvs or face normals, the only useful sharing you can get is between each 2 triangles in a quad.

That all works fine btw.

If your manual merging strategy is producing β€œmissing” triangles.. my first guess would be that the winding order is getting trashed, or not implemented correctly and some of the triangles are getting flipped.

If you switch to material.side = DoubleSide do the triangles come back?

Haha beat me to it. :smiley:

1 Like

Thanks guys! Sorry to waste your time. I spent HOURS driving myself crazy with this. I set up an entire new project just to test theories. Turns out the problem was I was building the three pieces of wall (outer top inner) then doing:

//let merged =  MergeGeometries.mergeBufferGeometries(\[
//    geoInner,
//    geoTop,
//    geoOuter
//\]);

and this was breaking it with the β€˜indexed’ triangles because I didn’t realize it was a destructive operation merging and eating my vertices.

I switched to:

// safe merge (no weld) - combines groups without welding, must all be indexed (smooth) or not (sharp) design objects
let merged = BufferGeometryUtils.mergeGeometries(
    \[geoInner, geoTop, geoOuter\],
    true // useGroups: lets you assign different materials per part if you want
);

which solved the problem then as that is more what I meant to do.

So there was nothing wrong with my drawing/meshing method. It was a red herring. I was just destroying everything myself after without realizing it. :melting_face:

1 Like

You’re not alone! I don’t want to list here how often I break things myself and then come up with the wildest theories and attempts to fix them. And then it often turns out to be a minor issue, a simple careless mistake. :upside_down_face:

1 Like