Access to faces in BufferGeometry - three.js

geometry.faces accessible only for new THREE.BoxGeometry. Then I try to use THREE.BoxBufferGeometry I can't change color for faces.
Not working:
var geometry = new THREE.BoxBufferGeometry( 100, 100, 100 );
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[ i ].color.setHex( Math.random() * 0xffffff );
}
Working:
var geometry = new THREE.BoxGeometry( 100, 100, 100 );
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[ i ].color.setHex( Math.random() * 0xffffff );
}

BufferGeometries in three.js are fundamentally different from the regular Geometries. They are not oriented towards ease of manipulation but rather towards how meshes need to be delivered to the WebGL API.
That being said, there is no explicit notion of "faces" for BufferGeometries, they are implicit. A BufferGeometry consists of a number of attributes (for background see here), one of them is the position-attribute.
In a regular BufferGeometry (as opposed to "indexed"), the faces are stored as sequences of three vertices within that attribute (something like [x1, y1, z1, x2, y2, z2, x3, ...], so for the first face position[0] is the x-component of the first vertex and position[8] is the z-component of the third vertex). All other attributes use a similar indexing-scheme. If you define an attribute color for the geometry, you can control the face-colors by writing the same color-value at the positions of all three face-vertices (so in this example a color-attribute with [r, g, b, r, g, b, r, g, b, ...] would assign the same rgb-value to the three vertices of the first triangle).
Indexed geometries are different: Instead of repeating the vertices for all triangles, every vertex is stored only once. An additional attribute index is used to connect the vertices into triangles. So an index-attribute might look like this: [0, 1, 2, 0, 2, 3, ...] which reads as "construct first triangle from vertices at positions 0, 1 and 2" and so on.
As this is a very efficient way of storing geometries, this technique is used with most (maybe even all) of the builtin geometries in three.js.
With indexed geometries it is not possible to have colors per face-vertex because the vertex must have the same color everywhere it is used. You can however use bufferGeometry.toNonIndexed() to convert an indexed geometry into a regular one.

All necesarry here https://threejs.org/docs/index.html#api/en/core/BufferGeometry
See examples: Mesh with non-indexed faces, Mesh with indexed faces...
and i think that little example be more useful:
const geometry = new THREE.BoxBufferGeometry( 100, 100, 100 );
const colorsAttr = geometry.attributes.position.clone();
// Faces will be colored by vertex colors
geometry.setAttribute('color', colorsAttr);
const material = new THREE.MeshBasicMaterial({
vertexColors: THREE.VertexColors
});
const cube = new THREE.Mesh( geometry, material );

Related

Why is three.js inconsistent about gouraud interpolation?

I want to shade a THREE.BoxBufferGeometry using a simple THREE.MeshLambertMaterial. The material is supposed to use a Lambert illumination model to pick the colors for each vertex (and it does), and then use Gouraud shading to produce smooth gradients on each face.
The Gouraud part is not happening. Instead, the cube's faces are each shaded with one single, solid color.
I have tried various other BufferGeometrys, and gotten inconsistent results.
For example, if instead I make an IcosahedronBufferGeometry, I get the same problem: each face is one single, solid color.
geometry = new THREE.IcosahedronBufferGeometry(2, 0); // no Gouraud shading.
geometry = new THREE.IcosahedronBufferGeometry(2, 2); // no Gouraud shading.
On the other hand, if I make a SphereBufferGeometry, the Gouraud is present.
geometry = new THREE.SphereBufferGeometry(2, 3, 2); // yes Gouraud shading.
geometry = new THREE.SphereBufferGeometry(2, 16, 16); // yes Gouraud shading.
But then if I make a cube using a PolyhedronBufferGeometry, the Gouraud shading doesn't appear unless I set the detail to something other than 0.
const verticesOfCube = [
-1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1,
-1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1,
];
const indicesOfFaces = [
2,1,0, 0,3,2,
0,4,7, 7,3,0,
0,1,5, 5,4,0,
1,2,6, 6,5,1,
2,3,7, 7,6,2,
4,5,6, 6,7,4
];
const geometry = new THREE.PolyhedronBufferGeometry(verticesOfCube, indicesOfFaces, 1, 1); // no Gouraud shading
geometry = new THREE.PolyhedronBufferGeometry(verticesOfCube, indicesOfFaces, 1, 1); // yes Gouraud shading
I am aware of the existence of the BufferGeometry methods computeFaceNormals() and computeVertexNormals(). Normals are emphatically important here, as they are used to determine the colors for each face and vertice, respectively. But while they help with the Icosahedron, they have no effect on the Box, no matter whether they are present, only one is present, or both are present in both possible orders.
Here is the code I expect to work:
const geometry = new THREE.BoxBufferGeometry(2, 2, 2);
geometry.computeFaceNormals();
geometry.computeVertexNormals();
const material = new THREE.MeshLambertMaterial({
color: 0xBE6E37
});
const mesh = new THREE.Mesh(geometry, material);
I should be getting a cube whose faces (the real, triangular ones) are shaded with a gradient. First, the face normals should be computed, and then the vertex normals by averaging the normals of the faces formed by them. Here is a triangular bipyramid on which correct Gouraud shading is being applied:
But the code above produces this instead:
At no point does three.js log any errors or warnings to the console.
So what is it that's going on here? The only explanation I can think of is that the Box is actually comprised of 24 vertices, three at each corner of the cube, and that they form faces such that each vertex's computed normal is an average of at most two faces pointing in the same direction. But I can't find that written down anywhere, and that explanation doesn't fly for the Polyhedron where vertices and faces were explicitly specified in code.

Problem in understanding three.js coordinate and axes system

I have following code:
// coordinate values
var x1 = -815723.5125568421;
var y1 = 20538442.534868136;
var z1 = -17.439584224846456;
var x2 = -815723.5125568421;
var y2 = 20538443.164575472;
var z2 = -16.620415776398275;
// make a rectangular face parallel to y-z plane
var dummySquare = new THREE.Geometry();
dummySquare.vertices.push(new THREE.Vector3(x1,y1,z1));
dummySquare.vertices.push(new THREE.Vector3(x1,y1,z2));
dummySquare.vertices.push(new THREE.Vector3(x2,y2,z1));
dummySquare.vertices.push(new THREE.Vector3(x2,y2,z2));
dummySquare.faces.push(new THREE.Face3(0,1,2));
dummySquare.faces.push(new THREE.Face3(1,2,3));
var dummySquareMaterial = new THREE.MeshBasicMaterial( { color: "#0000FF", side: THREE.DoubleSide } );
var dummySquareMesh = new THREE.Mesh(dummySquare, dummySquareMaterial);
So, I am making a rectangular face parallel to y-z plane.
During debugging I observe following:
vertices: Array(4)
0: p {x: -815723.5125568421, y: 20538442.534868136, z:
-17.439584224846456}
1: p {x: -815723.5125568421, y: 20538442.534868136, z:
-16.620415776398275}
2: p {x: -815723.5125568421, y: 20538443.164575472, z:
-17.439584224846456}
3: p {x: -815723.5125568421, y: 20538443.164575472, z:
-16.620415776398275}
position: p {x: 0, y: 0, z: 0}
So vertices are as expected. But position is at (0,0,0). I expected position to be mid point of the plane defined by above four vertices.
What is missing here in my understanding?
Another observation is as follows.
I make two faces just like above(same vertices).
For one of the two faces, I determine centre of geometry, move geometry it to origin(translate by negative of centre), create a mesh with it which then I move back to original position:
var face = new THREE.Geometry();
....add vertices as code snippet above
var faceCentre = new THREE.Vector3();
face.boundingBox.getCenter(faceCentre );
face.translate(-faceCentre .x,-faceCentre .y,-faceCentre .z);
//make mesh
var faceMaterial = new THREE.MeshBasicMaterial( { color: "#FF0000", side:
THREE.DoubleSide } );
var faceMesh= new THREE.Mesh(face, faceMaterial);
// move mesh back by setting its position to original centre of face
faceMesh.position.x = faceCentre .x;
faceMesh.position.y = faceCentre .y;
faceMesh.position.z = faceCentre .z;
Unmoved face has same vertices as for face above, as expected.
But other face has now totally different vertices, even though both are displayed at same position and in same orientation.
Why this difference in vertices?
THREE.js uses a hierarchical representation of objects and their translation. In particular, the .position of an Object3D is not generally, as you say you expected, its middle point in world space, but it can be viewed as a variable that stores the current translation of the object. It is called its local position. This translation is (0,0,0) on default.
So when you define an object by the vertices of its geometry, the object's vertices will render at these positions. However, if you .translate() it by a factor (dx, dy, dz), then a vertex (vx, vy, vz) will render at position (vx+dx, vy+dy, vz+dz).
Similarly, other transformations are also stored as members of the object. The vertices of the geometry do not change when an object is transformed, but instead the object keeps track of its current local transformations, which are applied, typically as a series of matrix multiplications, to the vertices. Using this logic, you can define a tree of objects inside each other, which have local transformations in relation to its parent, which in turn may be transformed in relation to its parent etc. This sort of representation proves very useful for slightly more complicated scenes.
This should explain your results. For example, in your first test, you are successfully creating an object exactly where you want it, but its position is still (0,0,0) because it has undergone no transformations.

Generate shape in three.js from multiple elements

Is there a way in three.js to create a poly from multiple individual elements, rectangle for example.
I have attached an example.
I am using:
for(i = 0; i<5; i++){
var rand = Math.floor(Math.random() * 50)+1000;
var material = new THREE.MeshBasicMaterial({
color : "#ff"+i+ rand,
side : THREE.DoubleSide,
transparent : true,
opacity : 1
});
var mesh = new THREE.Mesh( geometry, material );
if(angle) mesh.rotation.y = angle;
mesh.position.set( loop+1, 4,4);
scene.add( mesh );
}
When I apply roatation mesh.rotation.y = angle; it doesn't come up with my below design, I rather get a cross + because the panel rotates on it's y from center, not from corner...
Thank you
The
There are 3 ways to achieve what you're trying to do. The problem you are facing stems from transform origin, as you noted, origin defaults to position [0,0,0]. So, your options are:
build a transform matrix using a different transform offset for rotation, this is probably an overkill for simple use-cases.
translate geometry to not be centered on [0,0,0], for example you can move the whole quad (your geometry) right so that the left edge of the quad aligns with [0,0,0], then, when you rotate, left edge will stay put.
embed Mesh inside a Group, rotate the Mesh and translate (position.set(....)) the Group.
no matter which route you take - you will still have to deal with the some trigonometry as you will need to compute the position for the next segment to align with the edge of the previous one.
One more way around that is to build the following type of structure
Group[
Mesh 1,
Mesh 2,
Mesh 3,
Group [
Mesh 4,
Mesh 5,
Mesh 6,
Group [
Mesh 7
]
]
]
Last group is unnecessary, it's there purely for consistency.
As far as the trigonometry that I mentioned - it's simple Sin and Cos stuff, so it should be quite simple. Here is some pseudo-code that you'll need:
prevPosition, prevAngle //position and angle of previous segment
// Compute next segment transform
nextPosition.x = Math.cos(prevAngle)*segmentSize + prevPosition.x;
nextPosition.z = Math.sin(prevAngle)*segmentSize + prevPosition.z;

How to create geometry for thick lines with three.js?

I need to create thick lines in 3d that receive shadows. The lines are on a flat plane as in y is the same for all points. The lines all face up. An example would be the yellow line on a road.
I think the best way would be to create the geometry and use MeshLambertMaterial so that it would receive shadows.
Can anyone point me in the right direction to create the geometry from points? I assume I need to create the vertices with BufferGeometry.
I'm not quite sure if you already have an array of points (coordinates) for each vertex of the lines and want to create the line from them, or if you just want to create lines of a width and length that you can specify as your question is quite unclear, so I'll provide an example for the latter (and more simple) option.
If you don't have any specified vertices, you can create an object that looks like a "line" using a PlaneGeometry and specifying it's width and height.
i.e.
var geomLine = new THREE.PlaneGeometry(width, height, widthSegments, heightSegments);
var matLine = new THREE.MeshPhongMaterial({color: 0xFF0000});
var Line = new THREE.Mesh(geomLine, matLine);
Line.receiveShadow = true;
Line.castShadow = true;
scene.add(Line);
If you want the line to have some height and not be a flat plane, you could use a BoxGeometry instead of a LineGeometry. The code above would almost be the same, the only difference being you would also need to specify a depth for the box geometry, for example:
geomLine = new THREE.BoxGeometry(width, height, depth);
Bare in mind that the height for the BoxGeometry is how tall it is (on the y axis, the depth value is how "long" it is on the z axis)
EDIT: Here's how to create a polygon with pre-defined vertices
You can create a polygon from vertices with the following code:
var geom = new THREE.Geometry();
var v1 = new THREE.Vector3(0,0,0);
var v2 = new THREE.Vector3(0,500,0);
var v3 = new THREE.Vector3(0,500,500);
geom.vertices.push(v1);
geom.vertices.push(v2);
geom.vertices.push(v3);
geom.faces.push( new THREE.Face3( 0, 1, 2 ) );
geom.computeFaceNormals();
var object = new THREE.Mesh( geom, new THREE.MeshNormalMaterial() );
scene.add(object);
Copy and paste this code in and then change x, y, and z coordinates of v1, v2, and v3 (or however many vertices you need) to the coordinates of your vertices.
Essentially you are creating vertices using THREE.Vector3 to supply the coordinates and then pushing them to the vertices property of an empty THREE.Geometry();
Code is from this answer
Hope this is what you were looking for!

How to list me vertex normals to the specified tops?

I do animation at which the part of tops moves. Thus lighting starts working incorrectly. For correct lighting it is necessary to change face.vertexNormals. At first I thought that it is enough
geometry.computeVertexNormals();
But it appeared, it does not absolutely that.
How to list me topmost vertex normals to the specified tops?
OR
How to list me the faces containing specified tops?
example here
Here example. But I need not only to see them, and to list and change in the program.
if (d<50) { var dist = 15 * Math.cos( d/20 - t );
geometry.vertices[i].z = dist; }
How to list me vertex normals for these tops?
Your plane is upside-down, which causes your vertex normals to point downward.
Set plane.rotation.x = -Math.PI/2;.
To see the normals, add
vnh = new THREE.VertexNormalsHelper( plane, 20, 0xff0000, 2 );
scene.add( vnh );
to your init() function, and in your animation loop call:
vnh.update();
three.js r.68

Resources