Changing Sphere Colors independently three.js - javascript

In my scene I have 7 spheres in a flower petal formation (6 on the perimeter and 1 in the center). I want to change the colors of each of these spheres randomly. Here is the code I used to try to do this:
function changeSphereColors() {
var hex, color, len = spheres.length;
var change = function(i) {
var hex = "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
color = new THREE.Color(hex);
spheres[i].material.color = color;
}
for (var i=0; i<len; i++) {
change(i);
}
}
var render = function() {
requestAnimationFrame(render);
rotateSpheres();
var num = Math.random();
if (num<0.1) {
changeSphereColors();
}
renderer.render(scene, camera);
}
render();
spheres is an array of length 7 that has each sphere in it. Since I loop through each sphere and assign a random hex color to each one, there should be 7 spheres of all different colors, but instead, all the spheres are the same color.
I looked at the output to see if each color was different and that the loop was going as expected, and everything seemed to be working fine. There was a different hex color generated on each iteration of the loop, and it was assigning the hex color to each sphere.
Why are all the spheres the same color?

It is likely because they are sharing the same material. Create a separate material for each.
Here's an example I created with 3 spheres. Two share the same material, one doesn't. If I set the colour of one of the spheres with the shared material the other changes too:
var geometry = new THREE.SphereGeometry(3, 50, 50, 0, Math.PI * 2, 0, Math.PI * 2);
var material1 = new THREE.MeshBasicMaterial();
var material2 = new THREE.MeshBasicMaterial();
var sphere = [new THREE.Mesh(geometry, material1), new THREE.Mesh(geometry, material1), new THREE.Mesh(geometry, material2)];
scene.add(sphere[0]);
scene.add(sphere[1]);
scene.add(sphere[2]);
var hex = "0x" + "000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
sphere[0].material.color.setHex(hex);
hex = "0x" + "000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
sphere[2].material.color.setHex(hex);
// sphere[0] & sphere[1] now share the same colour, even though I didn't set for sphere[1].
// Sphere[2] colour is unique.
http://jsfiddle.net/ebnfsa5h/3/

Related

Three.js - position object at vertex of another

I have an IcosahedronGeometry defined like this (with all the code about colors and non-position stuff omitted):
var radius = 200;
geometry = new THREE.IcosahedronGeometry(radius, 2);
var materials = [
new THREE.MeshPhongMaterial({}),
new THREE.MeshBasicMaterial({})
];
group1 = THREE.SceneUtils.createMultiMaterialObject(geometry, materials);
group1.position.x = 0;
// rotate a bit just so it spins off-axis
group1.rotation.x = -1.87;
Which creates an almost spherical, many-sided shape.
I want to place little spheres at just a few of the vertices of this shape. Let's say 10 spheres. I do this by copying 10 vertices into an array, like this:
var vertexArray = [];
for (var i = 0; i < 10; i++) {
vertexArray.push(geometry4.vertices[i])
}
then, I use the Vectors copied into vertexArray to set the positions off Sprites:
for (var i = 0; i < vertexArray.length; i++) {
var loader = new THREE.TextureLoader()
var spriteMaterial = new THREE.SpriteMaterial(
{
map: loader.load('/glow.png'),
blending: THREE.AdditiveBlending,
side: THREE.BackSide
})
var sprite = new THREE.Sprite(spriteMaterial)
sprite.position.set(vertexArray[i].x, vertexArray[i].y, vertexArray[i].z)
// do i need rotate these by the same amount?
sprite.rotation.x = -1.87
scene.add(sprite)
}
This all works fine, except that the Sprites don't line up with the actual vertices on the Icosahedron, they just sit randomly (seemingly) somewhere on the faces. Occasionally a Sprite will sit exactly on a vertex.
Am I copying the vertices wrong, or missing a step in here?
Add an object as a child of a parent object, and the child object will rotate with the parent.
Use this pattern, instead:
var sprite = new THREE.Sprite( spriteMaterial );
sprite.position.copy( vertexArray[i] );
group1.add( sprite );
three.js r.76

Merging meshes that have different colors

Using Three.js r75
I am trying to display cubes that change color depending on an integer value from green to red. I have tried multiple ways as I am stuck on this. I was unable to make cubeMat.material.color.setRGB work and creating a new Three.Color doesn't seem to work either. Please note I merge all the geometries at the end for one draw call. I am hoping this isn't the issue.
[UPDATE]
I am confirming the rgb values are set correctly with getStyle however they do not render correctly. All cube stacks should be different colors.
function colorData(percentage){
var rgbString = "",
r = parseInt(percentage * 25.5),
g = parseInt(((percentage * 25.5) - 255) * -1),
b = 0;
rgbString = "rgb("+r+","+g+",0)";
return rgbString;
}
...
var position = latLongToSphere(objectCoord[1], objectCoord[0], 300),
rgb = colorData(objectMag),
cubeColor = new THREE.Color(rgb),
cubeMat = new THREE.MeshBasicMaterial({color: cubeColor}),
cubeHeight = objectMag * 175,
cubeGeom = new THREE.BoxGeometry(3,3,cubeHeight,1,1,1),
cube = new THREE.Mesh(cubeGeom, cubeMat);
// set position of cube on globe, point to center, merge together for one draw call
cube.geometry.colorsNeedUpdate = true;
cube.position.set(position.x, position.y, position.z);
cube.lookAt(lookCenter);
cube.updateMatrix();
console.log(cube.material.color.getStyle());
geom.merge(cube.geometry, cube.matrix);
You are merging geometries so you can render with a single draw call and a single material, but you want each of the merged geometries to have a different color.
You can achieve that by defining vertexColors (or faceColor) in your geometry. Here is a pattern to follow:
// geometry
var geometry = new THREE.Geometry();
for ( var count = 0; count < 10; count ++ ) {
var geo = new THREE.BoxGeometry( 5, 5, 5 );
geo.translate( THREE.Math.randFloat( - 5, 5 ), THREE.Math.randFloat( - 5, 5 ), THREE.Math.randFloat( - 5, 5 ) );
var color = new THREE.Color().setHSL( Math.random(), 0.5, 0.5 );
for ( var i = 0; i < geo.faces.length; i ++ ) {
var face = geo.faces[ i ];
face.vertexColors.push( color, color, color ); // all the same in this case
//face.color.set( color ); // this works, too; use one or the other
}
geometry.merge( geo );
}
Then, when you specify the material for the merged geometry, set vertexColors like so:
// material
var material = new THREE.MeshPhongMaterial( {
color: 0xffffff,
vertexColors: THREE.VertexColors // or THREE.FaceColors, if defined
} );
Your geometry will be rendered with a single draw call. You can verify that by typing renderer.info into the console. renderer.info.render.calls should be 1.
three.js r.75
cubeMat.material.color.setRGB won't work because it's like you're calling the material twice (cubeMat and material), try this instead:
cube.material.color.setRGB( value, value, value );
Turns out if you merge the geometry the materials cant have different colors.
I had to set the face color of each cube before merging.
See
Changing material color on a merged mesh with three js
Three js materials of different colors are showing up as one color

Threejs PlaneGeometry doesn't receive shadows or reflections

I'm pretty new to 3d and to threejs and I can't figure out how I can get a PlaneGeometry to show individually illuminated polygons i.e. receive shadows or show reflection. What I basically do is taking a PlaneGeometry applying some noise to every z value of the vertices. Then I have a simple directional light in my scene which is supposed to make the emerging noise pattern on the plane visible. I tried different things like plane.castShadow = true or renderer.shadowMapEnabled = true without success. Am I just missing a simple option or is this way more complicated than I think?
Here's are the relevant pieces of my code
renderer.setSize(width, height);
renderer.setClearColor(0x111111, 1);
...
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.9);
directionalLight.position.set(10, 2, 20);
directionalLight.castShadow = true;
directionalLight.shadowCameraVisible = true;
scene.add( directionalLight );
var geometry = new THREE.PlaneGeometry(20, 20, segments, segments);
var index = 0;
for(var i=0; i < segments + 1; i++) {
for(var j=0; j < segments + 1; j++) {
zOffset = simplex.noise2D(i * xNoiseScale, j * yNoiseScale) * 5;
geometry.vertices[index].z = zOffset;
index++;
}
}
var material = new THREE.MeshLambertMaterial({
side: THREE.DoubleSide,
color: 0xf50066
});
var plane = new THREE.Mesh(geometry, material);
plane.rotation.x = -Math.PI / 2.35;
plane.castShadow = true;
plane.receiveShadow = true;
scene.add(plane);
This is the output I get. Obviously the plane is aware of the light because the bottom side is darker than the upper side but there is no sign of any individual polygons receiving individual lightening and no 3d structure is visible. Interestingly when I put in a different geometry like a BoxGeometry individual polygons are illuminated individually (see 2nd image). Any ideas?
Ok I figured it out thanks to this post. The trick is to use the THREE.FlatShading shader on the material. Important to note is that after every update of the vertices two things need to be done. Before rendering geometry.normalsNeedUpdate must be set to true so the renderer also incorporates the newly oriented vertices. Also geometry.computeFaceNormals() needs to be called before rendering because when you alter the vertices the normals are not the same anymore.

THREE.MeshBasicMaterial renders, but THREE.MeshLambertMaterial does not

I'm working on a project that makes a sort of randomized sheet. It stores arrays of x, y, and z coordinates and draws triangles between the points. You can see this working pretty well in this screenshot.
I used MeshBasicMaterial to make that sheet, but wanted to switch to MeshLambertMaterial to take advantage of lighting. When I try this, I get a sheet that looks like this.
This is the working Basic Mesh code on green tiles:
for(j = 0; j < h-1; j++) { //h is the number of tiles vertically
for(i = 0; i < w-1; i++) { //w is the number of tiles horizontally
o = ((j%2==1)?1:0); //checks if the row is odd
var geom = new THREE.Geometry();
var a = new THREE.Vector3(x[i][j], y[i][j] ,z[i][j]);
var b = new THREE.Vector3(x[i+1][j], y[i+1][j] ,z[i+1][j]);
var c = new THREE.Vector3(x[i+o][j+1], y[i+o][j+1] ,z[i+o][j+1]);
geom.vertices.push(a);
geom.vertices.push(b);
geom.vertices.push(c);
geom.faces.push(new THREE.Face3(0,1,2));
tile1[i][j] = new THREE.Mesh(
geom,
new THREE.MeshBasicMaterial({color: 'green'})
);
scene.add(tile1[i][j]);
}
}
And this is the failing Lambert Mesh code on red tiles (note that I only changed 'Basic' to 'Lambert'):
for(j = 0; j < h-1; j++) { //h is the number of tiles vertically
for(i = 0; i < w-1; i++) { //w is the number of tiles horizontally
o = ((j%2==1)?0:1); //checks if the row is even
var geom = new THREE.Geometry();
var a = new THREE.Vector3(x[i+o][j], y[i+o][j] ,z[i+o][j]);
var b = new THREE.Vector3(x[i+1][j+1], y[i+1][j+1] ,z[i+1][j+1]);
var c = new THREE.Vector3(x[i][j+1], y[i][j+1] ,z[i][j+1]);
geom.vertices.push(a);
geom.vertices.push(b);
geom.vertices.push(c);
geom.faces.push(new THREE.Face3(0,1,2));
tile2[i][j] = new THREE.Mesh(
geom,
new THREE.MeshLambertMaterial({color: 'red'})
);
scene.add(tile2[i][j]);
}
}
A cube created with the following Lambert Mesh works perfectly and catches light properly.
scene.add(new THREE.Mesh(new THREE.BoxGeometry(10,1000,5),new THREE.MeshLambertMaterial({color:'red'})));
Why does the Lambert Mesh not work on a geometry that Basic Mesh works on?
EDIT: I placed a colored box under the sheet to test how the box would react to lighting and found that the tiles above it weren't failing to render, but were just black. They are opaque, but don't use the color or pick up light the way the box does.
You should have lights in your scene to profit from THREE.LambertMaterial. Did you setup your scene lighting correctly?
EDIT:
I found out where your problem is. You should add a normal to your faces, otherwise the WebGL renderer does not know how to render the light bouncing of the THREE.LambertMaterial on the surfaces. So change your code like this:
face = new THREE.Face3( 0, 1, 2 );
face.normal = new THREE.Vector3().crossVectors(
new THREE.Vector3().subVectors( b, a ),
new THREE.Vector3().subVectors( c, b )
).normalize();
geom.faces.push( face );
Now your faces should render.
Instead of doing this manually you can also use the geometry methods for calculating them:
geometry.computeFaceNormals();
geometry.computeVertexNormals();

Three js materials of different colors are showing up as one color

Three js ver67
I have a 3D globe where I am trying to add cubes of different materials at every coordinate. A Quantile distribution is used to assign colors to density cubes (the height of the cube at a certain coordinate is determined by the population density at that coordinate). The function below adds density cubes to the scene –
function addDensity() {
var materials = [];
var totalGeom = new THREE.Geometry();
var cubeMat;
for (var i = 0; i < dataSetArray.length; i++) {
var ptColor = dataSetArray[i].color;
var value = dataSetArray[i].value;
var position = latLongToVector3(dataSetArray[i].y, dataSetArray[i].x, 600, 1);
var cubeGeom = new THREE.BoxGeometry(5, 5, 1 + value / 20);
cubeMat = new THREE.MeshLambertMaterial({
color: new THREE.Color(ptColor),
opacity: 0.6
});
materials.push(cubeMat);
var cubeMesh = new THREE.Mesh(cubeGeom, cubeMat);
cubeMesh.position.copy(position);
cubeMesh.lookAt(scene.position);
cubeMesh.updateMatrix();
totalGeom.merge(cubeMesh.geometry, cubeMesh.matrix);
}
var total = new THREE.Mesh(totalGeom, new THREE.MeshFaceMaterial(materials));
scene.add(total);
}
dataSetArray is single dimension array of objects with properties – x,y,value,color. The value is the population density value and color is the assigned color. I have 7 colors –
var groupColors = [
"rgb(255,0,0)",
"rgb(255,127,0)",
"rgb(255,255,0)",
"rgb(0,255,0)",
"rgb(0,0,255)",
"rgb(75,0,130)",
"rgb(143,0,255)"
];
Hence 7 buckets in my distribution. I should be seeing an equal distribution of 7 colors of density cubes on my globe. However I am always seeing one color, the first one rgb(255,0,0). I am at my wits end with this one. Not sure if I am making a simple mistake. Btw, I have checked and checked again the contents of dataSetArray. The color fields have the right colors. The colors are appropriately distributed among all the elements of the array.
Any help will be appreciated a lot!

Categories