The Face3 constructor in three.js

The Face3 constructor in three.js is used to define a Face when making a custom geometry. When using any kind of built in geometry, instances of Face3 are created automatically, but whenever making a custom geometry from code, or trying to figure out some problems that may exist with how faces are being rendered it is necessary to understand a few things about Face3.

1 - What to know before you continue reading

This is an advanced post on three.js which is a javaScript library that is used to work with things in 3d space. If you are new to three.js you might want to start with my getting started post on three.js first. As of this writing three.js is a project that is still being developed fairly fast, so version numbers are of great concern, in this post I am using three.js 0.91.0 (r91).

Face3 is just one of several constructors of interest when making a custom geometry. Other constructors of interest are Vector3, and of course Geometry.

2 - Basic Example of Face3

For a basic demo of face3 I put together an example where I am just making a single triangle from an array of just three vertices. The Geometry constructor is used to create an instance of geometry, once I have that I will want to populate the instance of geometry with vertices by adding an array of Vector3 instances. Vector3 of course is another constructor that is used in three.js to create a point in space.

Once I have an array of vertices I will want a way to define faces that exist between them, this is where Face3 comes into play. The first three arguments given to Face3 are the index values in the vertices array that I want to make a triangular face with. The order of the index values does matter as it is used to determine the orientation of the face when it comes to rendering a texture to the face, more on that later.

So for now I have something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
(function () {
// SCENE
var scene = new THREE.Scene();
// CAMERA
var camera = new THREE.PerspectiveCamera(50, 4 / 3, .5, 1000);
camera.position.set(3, 2, 1);
camera.lookAt(0, 0, 0);
// GEOMETRY
var geometry = new THREE.Geometry();
// vertices made with Vector3
geometry.vertices = [
new THREE.Vector3(-1, -1, 0),
new THREE.Vector3(1, -1, 0),
new THREE.Vector3(1, 1, 0)
];
// face 3 arguments assigned to variable with comments
var a = 0, // vert index a
b = 1, // vert index b
c = 2, // vert index c
normal = new THREE.Vector3(0, 0, 1), // this sets the face normal
color = new THREE.Color(0xffaa00), // sets a face color
materialIndex = 0, // useful when working with an array of materials
// FACE3 example
face = new THREE.Face3(a, b, c, normal, color, materialIndex);
//add the face to the geometry's faces array
geometry.faces.push(face);
// compute face and vertex normals
geometry.computeVertexNormals();
geometry.computeFaceNormals();
// create a mesh using the geometry
var mesh = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
scene.add(mesh);
// adding face and vertex normals helper so I can
// see what is going on with the normals
scene.add(new THREE.FaceNormalsHelper(mesh, 2, 0x00ff00, 1));
scene.add(new THREE.VertexNormalsHelper(mesh, 2, 0xff0000, 1));
// RENDER
var renderer = new THREE.WebGLRenderer();
renderer.setSize(320, 240);
document.getElementById('demo').appendChild(renderer.domElement);
renderer.render(scene, camera);
}
());

3 - The order of indexes with face3

To some extent when making faces I am just playing connect the dots with vertices, but it is not always just that simple, as the order of index values does matter. When creating a mesh with the geometry, I also give a material. When it comes to materials there is the side property of a material which is used to set which side of a face3 instance that is to be rendered with the material. This property expects an integer value the default of which is stored in the constant THREE.FrontSide which as of this writing is a value of zero.

What I am driving at here is that the order of the indexes is what is used to find out what side of the face is the front side. If you are running into some kind of weird issue where some of your faces are rendering and others are not it could be because you are not getting the index order right.

There are two ways of fixing this one is to just make it so both sides are always rendered no matter what by setting the side value of your material to THREE.DoubleSide. This will make it so that both sides of the face are always rendered with the material, but the best way of fixing this would be to just get the index order right.

3.1 - Setting Three.DoubleSide for the side property of the material

1
2
3
4
var mesh = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial({
side: THREE.DoubleSide
}));
scene.add(mesh);

This is also just a useful property to be aware of for use with certain Models anyway, for example if I have a plain and I want a material rendered on both sides.

3.2 - Just getting the vertex index order right for the Face3 instances

The other way is to just get the index values right in which case the default THREE.FrontSide is not a problem when rendering.

Consider the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
geometry.vertices.push(
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(1, 0, 0),
new THREE.Vector3(1, 1, 0),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(-1, 0, 0),
new THREE.Vector3(-1, -1, 0));
// FACE3
geometry.faces.push(
new THREE.Face3(0, 1, 2),
new THREE.Face3(5, 4, 3));

Notice that with the first instance of Face3 I am starting with index 0 then counting up, while with the other instance I am staring with the last index and counting backwards. This results in the Front side of both faces being on opposite sides relative to each other.

4 - The Material index property

If in case you did not know, it is possible to give an array of materials to the mesh constructor, rather than just one. In this case there should be some way to set which material should be used for which insistence of face3. For this there is the material index property of face3.

Say for example I want to have a cube with three different materials that will each be used for three of the six sides of the cube. To pull that off I might do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// GEOMETRY
var geometry = new THREE.BoxGeometry(1, 1, 1);
geometry.faces.forEach(function (face3, i) {
face3.materialIndex = Math.floor(i % 6 / 2)
});
var mesh = new THREE.Mesh(
// geometry as first argument
geometry,
[
new THREE.MeshBasicMaterial({
color: 0xff0000
}),
new THREE.MeshBasicMaterial({
color: 0x00ff00
}),
new THREE.MeshBasicMaterial({
color: 0x0000ff
})
]);
scene.add(mesh);

The value that I give to material index should be the index value of the material I want to use in the array of materials.