0
votes

I'm making a custom three.js geometry for non-orthogonal cubes. It is loosely based on the existing Box-geometry in three.js but has the absolute position of its vertices fed directly to it.

I have a problem with three.js seemingly not respecting the normals that I pass to the faces of the geometry. When I console.log the normals just prior to assigning them to the faces, they look just fine:

normal: Object { x=0, y=0, z=-1, more...}
normal: Object { x=0, y=0, z=1, more...}

These normals would be just fit for a straight cube's upper and lower sides. However, as can be seen from this screenshot (were I have tilted the camera appropriately), the first normal seems to have been applied to both surfaces:

a cube of 200x200x200 centred on origo and orthogonal to all axes

This is two sides of a cube of 200x200x200 centred on origo and orthogonal to all axes. The white line denotes the z-axis.

Here follows my faulty geometry. The 'quadruplets' array contains six arrays with each four Vector3 instances. Each inner array delineates a side of the cube.

THREE.Box3Geometry = function (quadruplets, normals, debug) {

THREE.Geometry.call(this);

var constructee = this;  // constructee = the instance currently being constructed by the Box3Geometry constructor

buildPlane(quadruplets[0], normals[0], 0, debug); // px
buildPlane(quadruplets[1], normals[1], 1, debug); // nx
//    buildPlane(quadruplets[2], normals[2], 2); // py
//    buildPlane(quadruplets[3], normals[3], 3); // ny
//    buildPlane(quadruplets[4], normals[4], 4); // pz
//    buildPlane(quadruplets[5], normals[5], 5); // nz

function buildPlane(quadruplet, normal, materialIndex, debug) {


    var offset = constructee.vertices.length;

    // populate the vertex array:
    constructee.vertices.push(quadruplet[0]);
    constructee.vertices.push(quadruplet[1]);
    constructee.vertices.push(quadruplet[2]);
    constructee.vertices.push(quadruplet[3]);

    // construct faceVertexUvs:
    var uva = new THREE.Vector2(0, 1);  //(u:0, v:1), uvb: (u:0, v:0) uvc: (u:1, v:0), uvd: (u:1, v:1)
    var uvb = new THREE.Vector2(0, 0);
    var uvc = new THREE.Vector2(1, 0);
    var uvd = new THREE.Vector2(1, 1);

    // construct faces:
    var a = 0;  // vertex: u:50, v:50
    var b = 2;  // vertex: u:50, v:-50
    var c = 3;  // vertex: u:-50, v:-50
    var d = 1;  // vertex: u:-50, v:50

    if (debug) { console.log("normal: ", normal); }

    // make a face:
    var face1 = new THREE.Face3(a + offset, b + offset, d + offset);
    face1.normal.copy(normal);
    face1.vertexNormals.push(normal.clone(), normal.clone(), normal.clone());
    face1.materialIndex = materialIndex;
    constructee.faces.push(face1);
    constructee.faceVertexUvs[ 0 ].push([ uva, uvb, uvd ]);

    // make another face:
    var face2 = new THREE.Face3(b + offset, c + offset, d + offset);
    face2.normal.copy(normal);
    face2.vertexNormals.push(normal.clone(), normal.clone(), normal.clone());
    face2.materialIndex = materialIndex;
    constructee.faces.push(face2);
    constructee.faceVertexUvs[ 0 ].push([ uvb.clone(), uvc, uvd.clone() ]);

};

this.mergeVertices();
};

THREE.Box3Geometry.prototype = Object.create(THREE.Geometry.prototype);

This is my test instantiation of the geometry:

var quadruplets = [
    [new THREE.Vector3(-100, -100, -100), new THREE.Vector3(100, -100, -100), new THREE.Vector3(-100, 100, -100), new THREE.Vector3(100, 100, -100)],
    [new THREE.Vector3(-100, -100,  100), new THREE.Vector3(100, -100,  100), new THREE.Vector3(-100, 100,  100), new THREE.Vector3(100, 100,  100)],
    [new THREE.Vector3(-100, -100, -100), new THREE.Vector3(100, -100, -100), new THREE.Vector3(-100, -100, 100), new THREE.Vector3(100, -100, 100)],
    [new THREE.Vector3(-100,  100, -100), new THREE.Vector3(100,  100, -100), new THREE.Vector3(-100,  100, 100), new THREE.Vector3(100,  100, 100)],
    [new THREE.Vector3(-100, -100, -100), new THREE.Vector3(-100, 100, -100), new THREE.Vector3(-100, -100, 100), new THREE.Vector3(-100, 100, 100)],
    [new THREE.Vector3( 100, -100, -100), new THREE.Vector3( 100, 100, -100), new THREE.Vector3( 100, -100, 100), new THREE.Vector3( 100, 100, 100)]
];

var normals = [
    new THREE.Vector3( 0,  0, -1),
    new THREE.Vector3( 0,  0,  1),
    new THREE.Vector3( 0, -1,  0),
    new THREE.Vector3( 0,  1,  0),
    new THREE.Vector3(-1,  0,  0),
    new THREE.Vector3( 1,  0,  0)
];

var myBox3Geometry = new THREE.Box3Geometry(
    quadruplets,
    normals,
    true
);

And this is the Box geometry from which I was "inspired".

2
I am having trouble decoding your screenshot. You say it illustrates the wrong normals, but all I can see both polygons facing the same way due to polygon winding (i.e. "the order of the vertex indices"). Normals only affect lighting.Paul-Jan

2 Answers

2
votes

Three.js has some very nice Helper functions:

THREE.BoxHelper( mesh )
THREE.FaceNormalsHelper( mesh )
THREE.VertexNormalsHelper( mesh )
THREE.WireframeHelper( mesh )

just to name a few.

It looks like you are setting vertex normals so why don't you use the THREE.VertexNormalsHelper(mesh) to verify three.js is using what you expect.

Note: Your variable normal is not visible in your code.

0
votes

I found the answer to the problem in the answer to the question Issue with custom geometry and face normal by mrdoob.

So in short, one has to change the order in which the vertices are given to the faces, in order to control which way the normals are pointing.

Thus, my code now looks like this:

THREE.Box3Geometry = function (quadruplets, normals, debug) {

THREE.Geometry.call(this);

var constructee = this;  // constructee = the instance currently being constructed by the Box3Geometry constructor

buildPlane(quadruplets[0], normals[0], 0, debug); // px
buildPlane(quadruplets[1], normals[1], 1, debug); // nx
buildPlane(quadruplets[2], normals[2], 2); // py
buildPlane(quadruplets[3], normals[3], 3); // ny
buildPlane(quadruplets[4], normals[4], 4); // pz
buildPlane(quadruplets[5], normals[5], 5); // nz

function buildPlane(quadruplet, normal, materialIndex, debug) {

    if (debug) {
        console.log("materialIndex: ", materialIndex);
        console.log("normal: ", normal);
    }

    var offset = constructee.vertices.length;

    // populate the vertex array:
    constructee.vertices.push(quadruplet[0]);
    constructee.vertices.push(quadruplet[1]);
    constructee.vertices.push(quadruplet[2]);
    constructee.vertices.push(quadruplet[3]);

    // construct faceVertexUvs:
    var uva = new THREE.Vector2(0, 1);  //(u:0, v:1), uvb: (u:0, v:0) uvc: (u:1, v:0), uvd: (u:1, v:1)
    var uvb = new THREE.Vector2(0, 0);
    var uvc = new THREE.Vector2(1, 0);
    var uvd = new THREE.Vector2(1, 1);

    // construct faces:
    var a = 0;  // vertex: u:50, v:50
    var b = 2;  // vertex: u:50, v:-50
    var c = 3;  // vertex: u:-50, v:-50
    var d = 1;  // vertex: u:-50, v:50

    // decide the ordering of the face vertices:
    var reference_vertex = a + offset;
    var x = constructee.vertices[reference_vertex].clone().sub(normal);  // x: the normal relative to reference_vertex
    if (debug) { console.log("x: ", x); }
    var face_tangential_vector_b_a = constructee.vertices[b + offset].clone().sub(constructee.vertices[reference_vertex]);
    var face_tangential_vector_d_a = constructee.vertices[d + offset].clone().sub(constructee.vertices[reference_vertex]);
    var face_tangential_vector_x_a = x.sub(constructee.vertices[reference_vertex]);
    var matrix = utils.matrix_from_column_vectors(
        face_tangential_vector_b_a,
        face_tangential_vector_d_a,
        face_tangential_vector_x_a
    );
    var determinant = utils.matrix3Determinant(matrix);
    if (debug) { console.log("determinant: ", determinant); }

    // make two faces:
    if (determinant < 0) {
        var face1 = new THREE.Face3(a + offset, b + offset, d + offset);
        constructee.faceVertexUvs[ 0 ].push([ uva, uvb, uvd ]);
        var face2 = new THREE.Face3(b + offset, c + offset, d + offset);
        constructee.faceVertexUvs[ 0 ].push([ uvb.clone(), uvc, uvd.clone() ]);
    } else {
        var face1 = new THREE.Face3(a + offset, d + offset, b + offset);
        constructee.faceVertexUvs[ 0 ].push([ uva, uvd, uvb ]);
        var face2 = new THREE.Face3(b + offset, d + offset, c + offset);
        constructee.faceVertexUvs[ 0 ].push([ uvb.clone(), uvd.clone(), uvc ]);
    }

    face1.normal.copy(normal);
    face1.vertexNormals.push(normal.clone(), normal.clone(), normal.clone());
    face1.materialIndex = materialIndex;
    constructee.faces.push(face1);

    face2.normal.copy(normal);
    face2.vertexNormals.push(normal.clone(), normal.clone(), normal.clone());
    face2.materialIndex = materialIndex;
    constructee.faces.push(face2);

    if (false) {
        console.log("materialIndex: ", materialIndex);
        console.log("a: %d, vertex: x:%f, y:%f, z:%f", a, constructee.vertices[a][x], constructee.vertices[a][y], constructee.vertices[a][z]);
        console.log("b: %d, vertex: x:%f, y:%f, z:%f", b, constructee.vertices[b][x], constructee.vertices[b][y], constructee.vertices[b][z]);
        console.log("c: %d, vertex: x:%f, y:%f, z:%f", c, constructee.vertices[c][x], constructee.vertices[c][y], constructee.vertices[c][z]);
        console.log("d: %d, vertex: x:%f, y:%f, z:%f", d, constructee.vertices[d][x], constructee.vertices[d][y], constructee.vertices[d][z]);
        console.log("face1: a: (x:%f, y:%f, z:%f), b: (x:%f, y:%f, z:%f), d: (x:%f, y:%f, z:%f)",
    face1.a.getComponent[0], face1.a.getComponent[1], face1.a.getComponent[2],
    face1.b.getComponent[0], face1.b.getComponent[1], face1.b.getComponent[2],
    face1.c.getComponent[0], face1.c.getComponent[1], face1.c.getComponent[2]);
        console.log("face2: a: (u:%f, v:%f), c: (u:%f, v:%f), d: (u:%f, v:%f)",
    constructee.vertices[b + offset][u], constructee.vertices[b + offset][v],
    constructee.vertices[c + offset][u], constructee.vertices[c + offset][v],
    constructee.vertices[d + offset][u], constructee.vertices[d + offset][v]);
        console.log("uva: (u:%f, v:%f), uvb: (u:%f, v:%f) uvc: (u:%f, v:%f), uvd: (u:%f, v:%f)",
    uva.getComponent(0), uva.getComponent(1),
    uvb.getComponent(0), uvb.getComponent(1),
    uvc.getComponent(0), uvc.getComponent(1),
    uvd.getComponent(0), uvd.getComponent(1));
        console.log(" ");
    }


    if (false) {
        console.log("segUi: %d, segVi: %d, uDir: %d, vDir: %d", segUi, segVi, uDir, vDir);
        console.log("segmentDist_u: %f, segmentDist_v: %f", segmentDist_u, segmentDist_v);
        console.log(" vertex[ u ] = ( iu * segmentDist_u - uDist_half ) * uDir\n" +
    " vertex[ v ] = ( iv * segmentDist_v - vDist_half ) * vDir\n" +
    " vertex[ w ] = wDist_half");
        console.log(" "); }


};
this.mergeVertices();
};
THREE.Box3Geometry.prototype = Object.create(THREE.Geometry.prototype);