8

I have cloned and than flipped an object using negative scale, which causes my single sided faces to inverse. My question is, how can i flip the normals too?

I don't want to use material.side = THREE.DoubleSide, for reasons: 1) didn't work properly (some shades are drawn from inside) and 2) wanna keep as much performance as possible. So DoubleSide isn't an option for me.

Thats how my object if flipped.

mesh.scale.x = - scale_width;

Thanks in advance!

  • I read your answer to this question too and i thought what other choice do i have to "flip" my objects than negative scaling? Maybe you can advice me a better solution than scaling negatively? My task is to build a symetric object: a frame for an image for a virtual art gallery. So i have the geometry of one frame side, they can even vary in their form. Each frame side has two 45deg corners, round or sharp, whatever. So i create one corner and clone it, than i negatively scale it, to get a full flip. Any suggestion how to handle that without negative scale? – Markus Siebeneicher May 30 '13 at 08:21

5 Answers5

8

I would advise against negative scale for a whole host of reasons, as explained in this link: Transforming vertex normals in three.js

You can instead apply an inversion matrix to your geometry like so

geometry.scale( - 1, 1, 1 );

As explained in the link, a consequence to doing this, however, is the geometry faces will no longer have counterclockwise winding order, but clockwise.

You can manually traverse your geometry and flip the winding order of each face. This may work for you -- if you do not have a texture applied and are not using UVs. If your geometry is to be textured, the UVs will need to be corrected, too.

Actually, a geometry inversion utility would be a nice addition to three.js. Currently, what you want to do is not supported by the library.

three.js r.72

Community
  • 1
  • 1
WestLangley
  • 102,557
  • 10
  • 276
  • 276
6

Just dumping this here. I found somewhere flipNormals and translated it for BufferGeometry

Flip normals, flip UVs, Inverse Face winding

Version for indexed BufferGeometry

function flipBufferGeometryNormalsIndexed(geometry) {
    const index = geometry.index.array
    for (let i = 0, il = index.length / 3; i < il; i++) {
        let x = index[i * 3]
        index[i * 3] = index[i * 3 + 2]
        index[i * 3 + 2] = x
    }
    geometry.index.needsUpdate = true
}

Version for non-indexed BufferGeometry

export function flipBufferGeometryNormals(geometry) {
    const tempXYZ = [0, 0, 0];

    // flip normals
    for (let i = 0; i < geometry.attributes.normal.array.length / 9; i++) {
        // cache a coordinates
        tempXYZ[0] = geometry.attributes.normal.array[i * 9];
        tempXYZ[1] = geometry.attributes.normal.array[i * 9 + 1];
        tempXYZ[2] = geometry.attributes.normal.array[i * 9 + 2];

        // overwrite a with c
        geometry.attributes.normal.array[i * 9] =
            geometry.attributes.normal.array[i * 9 + 6];
        geometry.attributes.normal.array[i * 9 + 1] =
            geometry.attributes.normal.array[i * 9 + 7];
        geometry.attributes.normal.array[i * 9 + 2] =
            geometry.attributes.normal.array[i * 9 + 8];

        // overwrite c with stored a values
        geometry.attributes.normal.array[i * 9 + 6] = tempXYZ[0];
        geometry.attributes.normal.array[i * 9 + 7] = tempXYZ[1];
        geometry.attributes.normal.array[i * 9 + 8] = tempXYZ[2];
    }

    // change face winding order
    for (let i = 0; i < geometry.attributes.position.array.length / 9; i++) {
        // cache a coordinates
        tempXYZ[0] = geometry.attributes.position.array[i * 9];
        tempXYZ[1] = geometry.attributes.position.array[i * 9 + 1];
        tempXYZ[2] = geometry.attributes.position.array[i * 9 + 2];

        // overwrite a with c
        geometry.attributes.position.array[i * 9] =
            geometry.attributes.position.array[i * 9 + 6];
        geometry.attributes.position.array[i * 9 + 1] =
            geometry.attributes.position.array[i * 9 + 7];
        geometry.attributes.position.array[i * 9 + 2] =
            geometry.attributes.position.array[i * 9 + 8];

        // overwrite c with stored a values
        geometry.attributes.position.array[i * 9 + 6] = tempXYZ[0];
        geometry.attributes.position.array[i * 9 + 7] = tempXYZ[1];
        geometry.attributes.position.array[i * 9 + 8] = tempXYZ[2];
    }

    // flip UV coordinates
    for (let i = 0; i < geometry.attributes.uv.array.length / 6; i++) {
        // cache a coordinates
        tempXYZ[0] = geometry.attributes.uv.array[i * 6];
        tempXYZ[1] = geometry.attributes.uv.array[i * 6 + 1];

        // overwrite a with c
        geometry.attributes.uv.array[i * 6] =
            geometry.attributes.uv.array[i * 6 + 4];
        geometry.attributes.uv.array[i * 6 + 1] =
            geometry.attributes.uv.array[i * 6 + 5];

        // overwrite c with stored a values
        geometry.attributes.uv.array[i * 6 + 4] = tempXYZ[0];
        geometry.attributes.uv.array[i * 6 + 5] = tempXYZ[1];
    }

    geometry.attributes.normal.needsUpdate = true;
    geometry.attributes.position.needsUpdate = true;
    geometry.attributes.uv.needsUpdate = true;
}

For old style Geometry

export function flipNormals(geometry) {
    let temp = 0;
    let face;

    // flip every vertex normal in geometry by multiplying normal by -1
    for (let i = 0; i < geometry.faces.length; i++) {
        face = geometry.faces[i];
        face.normal.x = -1 * face.normal.x;
        face.normal.y = -1 * face.normal.y;
        face.normal.z = -1 * face.normal.z;
    }

    // change face winding order
    for (let i = 0; i < geometry.faces.length; i++) {
        const face = geometry.faces[i];
        temp = face.a;
        face.a = face.c;
        face.c = temp;
    }

    // flip UV coordinates
    const faceVertexUvs = geometry.faceVertexUvs[0];
    for (let i = 0; i < faceVertexUvs.length; i++) {
        temp = faceVertexUvs[i][0];
        faceVertexUvs[i][0] = faceVertexUvs[i][2];
        faceVertexUvs[i][2] = temp;
    }

    geometry.verticesNeedUpdate = true;
    geometry.normalsNeedUpdate = true;

    geometry.computeFaceNormals();
    geometry.computeVertexNormals();
    geometry.computeBoundingSphere();
}
Gangula
  • 5,193
  • 4
  • 30
  • 59
Pawel
  • 16,093
  • 5
  • 70
  • 73
  • Hello Pawel, the old style geometry one works, but no luck with the new BufferGeometry function. Has the format changed perhaps? Can you double check, and/or provide a working example? – trusktr Apr 18 '22 at 04:59
  • Hm, yeah, maybe the format changed, and it's not very well documented at all. Here's a demo without negative scale as references: https://codepen.io/trusktr/pen/ZEvVpra. Now here's the same with -1 scale for X: https://codepen.io/trusktr/pen/rNpoMJJ. Finally here's the attempt to apply the above function, and it totally breaks the cube: https://codepen.io/trusktr/pen/gOoZwKp. – trusktr Apr 18 '22 at 05:45
  • @trusktr This function only works for non-indexed geometries. I added another version for indexed ones which is much simpler – Pawel Apr 25 '22 at 12:37
  • Amazing. Thank you! Here is the demo with the indexed function working: https://codepen.io/trusktr/pen/ZErQZWg. This implies a related question: how do we detect and indexed vs non-indexed geometry to then call the correct function? – trusktr May 08 '22 at 23:37
  • 1
    @trusktr 1. Great! 2. `if (geometry.index) {}` – Pawel May 08 '22 at 23:45
3

This question is two years old, but in case anyone passes by. Here is a non-destructive way of doing this:

You can enter the "dirty vertices/normals" mode, and flip the normals manually:

mesh.geometry.dynamic = true
mesh.geometry.__dirtyVertices = true;
mesh.geometry.__dirtyNormals = true;

mesh.flipSided = true;

//flip every vertex normal in mesh by multiplying normal by -1
for(var i = 0; i<mesh.geometry.faces.length; i++) {
    mesh.geometry.faces[i].normal.x = -1*mesh.geometry.faces[i].normal.x;
    mesh.geometry.faces[i].normal.y = -1*mesh.geometry.faces[i].normal.y;
    mesh.geometry.faces[i].normal.z = -1*mesh.geometry.faces[i].normal.z;
}

mesh.geometry.computeVertexNormals();
mesh.geometry.computeFaceNormals();

+1 @WestLangley, I suggest you never use negative scale.

Ali Hammoud
  • 325
  • 2
  • 12
  • Interesting. But note that .flipSided is no longer part of THREE.js. See u/WestLangley answer here: http://stackoverflow.com/questions/19673913/three-js-flipsided-property. – steveOw Oct 02 '15 at 10:42
  • geometry.__dirtyVertices & geometry.__dirtyNormals are also also deprecated. See https://github.com/mrdoob/three.js/wiki/Updates – Reimund Aug 05 '16 at 05:56
3

It is fixed !!

The flip of an object with a negative scale object.scale.x = -1 also reverse the normals since three.js r89 (see: Support reflection matrices. #12787).

(But I have to upgrade to r91 to solve my normal issue.)

Jérémie Boulay
  • 400
  • 4
  • 13
1

If you have an indexed BufferGeometry it's already enough to reorder the indices like this:

    let temp;
    for ( let i = 0; i < geometry.index.array.length; i += 3 ) {
      // swap the first and third values
      temp = geometry.index.array[ i ];
      geometry.index.array[ i ] = geometry.index.array[ i + 2 ];
      geometry.index.array[ i + 2 ] = temp;
    }

Maccesch
  • 1,998
  • 1
  • 18
  • 27