I am using Three.js to generate a polyhedron with differing colors and text on each face, generated from a canvas element. For now, I'm sticking with polyhedra for which Three.js includes native classes, but at some point, I'd like to branch out into more irregular shapes.
There are a number of examples available online (including StackOverflow posts, like Three.js cube with different texture on each face) that explain how to do this with cubes. I haven't succeeded in finding any samples that show the same technique applied to non-cubes, but for the most part, the same process that works for CubeGeometry also works for TetrahedronGeometry and so forth.
Here's a simplified version of the code I'm using to generate the polyhedron:
switch (shape) {
case "ICOSAHEDRON" :
// Step 1: Create the appropriate geometry.
geometry = new THREE.IcosahedronGeometry(PolyHeatMap.GEOMETRY_CIRCUMRADIUS);
// Step 2: Create one material for each face, and combine them into one big
// MeshFaceMaterial.
material = new THREE.MeshFaceMaterial(createMaterials(20, textArray));
// Step 3: Pair each face with one of the materials.
for (x = 0; face = geometry.faces[x]; x++)
{
face.materialIndex = x;
}
break;
// And so on, for other shapes.
}
function createTexture (title, color) {
var canvas = document.createElement("canvas");
// Magical canvas generation happens here.
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return new THREE.MeshLambertMaterial({ map : texture });
}
function createMaterials (numFacets, textArray)
{
var materialsArray = [],
material;
for (var x = 0, xl = numFacets; x < xl; x++)
{
material = createTexture(textArray[x], generateColor(textArray[x]));
material.side = THREE.DoubleSide;
materials.push(oMaterial);
}
return materials;
}
Cubes render perfectly using this technique, but with other polyhedra, the textures do not behave as expected:
It's hard to explain precisely what's happening here. Essentially, each face is displaying the correct texture, but the texture itself has been stretched and shifted as if to cover the entire polyhedron. In other words - looking at the shape dead-on - the upper-left face is only showing the upper-left portion of its texture, the upper-right face is only showing the upper-right portion, and so on.
The faces on the opposite side of the polyhedron shows no texture detail at all; only colors.
I had no experience with 3D rendering prior to experimenting with Three.js, so I imagine that there's some step I'm missing that is handled automatically by CubeGeometry but not its sister classes. I'd refer to other examples that have been posted, but most examples are rendering cubes, and those that don't are usually using solid colors.
What needs to happen for the textures on the non-cube shapes to be scaled and centered properly?
You need to set new UVs.
I made a simple example how to do it, don't know if it's the best way.
jsFiddle example
Update
geometry.faceVertexUvs[0] = [];
for(var i = 0; i < geometry.faces.length; i++){
// set new coordinates, all faces will have same mapping.
geometry.faceVertexUvs[0].push([
new THREE.Vector2( 0,0 ),
new THREE.Vector2( 0,1 ),
new THREE.Vector2( 1,1),
]);
}
Related
I'm working on visualizing paths of lines in Three.JS and have successfully added a bunch of lines to the scene with the correct vertices, and material that I want but the lines are hard to see. Is there a way to convert a line segment into a tube of sorts without having to start from scratch and change the type of geometry I'm using?
I may not be using the correct terminology but I basically want to turn a generated THREE.LineSegments() into a thicker line in 3D. Below is a snippet of my code:
var geo = new THREE.BufferGeometry();
geo.addAttribute('position', new THREE.BufferAttribute(new Float32Array(2*numTravelVertices), 3));
var travelVertices = geo.attributes.position.array;
var vertIndex = 0;
this.set('travelVertices', travelVertices);
<add vertex indicies for points on the path>
geo.rotateX(-Math.PI / 2);
var mat = new THREE.LineBasicMaterial({color: parseInt(this.get('travelColor')), transparent: false});
var lineSegments = new THREE.LineSegments(geo, new THREE.MultiMaterial([mat]));
You can draw thick lines by setting the LineBasicMaterial linewidth parameter:
material.linewidth = 4; // default is 1
This currently does not work on some Windows platforms. So an alternate solution is to use the thrid-party class THREE.MeshLine, which renders thick lines by drawing a strip of triangles.
You can use THREE.TubeGeometry, but that would not be as performant as MeshLine.
three.js r.82
I'm working on a terrain generator, but I can't seen to figure out how to do the colors. I want to be able to generate an image that will take up my whole PlaneGeometry. My question is how can I create a single image that will cover the entire PlaneGeometry (with no wrapping) based off my height map? I can think of one way, but I'm not sure it would fully cover the PlaneGeometry and it would be very inefficient. I'd draw it in a two-dimensional view with colors on a canvas. I'd then convert the canvas to the texture Is that the best/only way?
UPDATE: Using DataTexture, I got some errors. I have absolutely no idea where I went wrong. Here's the error I got:
WebGL: drawElements: texture bound to texture unit 0 is not renderable. It maybe non-power-of-2 and have incompatible texture filtering or is not 'texture complete'. Or the texture is Float or Half Float type with linear filtering while OES_float_linear or OES_half_float_linear extension is not enabled.
Both the DataTexture and the PlaneGeometry have a size of 512^2. What can I do to fix this?
Here's some of the code I use:
EDIT: I fixed it. Here's the working code I used.
function genDataTexture(){
//Set the size.
var dataMap = new Uint8Array(1 << (Math.floor(Math.log(map.length * map[0].length * 4) / Math.log(2))));
/* ... */
//Set the r,g,b for each pixel, color determined above
dataMap[count++] = color.r;
dataMap[count++] = color.g;
dataMap[count++] = color.b;
dataMap[count++] = 255;
}
var texture = new THREE.DataTexture(dataMap, map.length, map[0].length, THREE.RGBAFormat);
texture.needsUpdate = true;
return texture;
}
/* ... */
//Create the material
var material = new THREE.MeshBasicMaterial({map: genDataTexture()});
//Here, I mesh it and add it to scene. I don't change anything after this.
The optimal way, if the data is already in your Javascript code, is to use a DataTexture -- see https://threejs.org/docs/#api/textures/DataTexture for the general docs, or look at THREE.ImageUtils.generateDataTexture() for a fairly-handy way to make them. http://threejs.org/docs/#Reference/Extras/ImageUtils
I have a scene with lots of objects using ExtrudeGeometry. Each of these need to update each frame, where the shape that is being extruded is changing, along with the amount of extrusion. The shapes are being generated using d3's voronoi algorithm.
See example.
Right now I am achieving this by removing every object from the scene and redrawing them each frame. This is very costly and causing performance issues. Is there a way to edit each mesh/geometry instead of removing from the scene? Would this help with performance? Or is there a more efficient way of redrawing the scene?
I'd need to edit both the shape of the extrusion and the amount of extrusion.
Thanks for taking a look!
If you're not changing the number of faces, you can use morph targets http://threejs.org/examples/webgl_morphtargets.html
You should
Create your geometry
Clone the geometry and make your modifications to it, such as the maximum length of your geometry pillar
Set both geometries as morph targets to your base geometry, for example
baseGeo.morphTargets.push(
{ name: "targetName", vertices: [ modifiedVertexArray ] }
);
After that, you can animate the mesh this using mesh.updateMorphTargets()
See http://threejs.org/examples/webgl_morphtargets.html
So I managed to come up with a way of not having to redraw the scene every time and it massively improved performance.
http://jsfiddle.net/x00xsdrt/4/
This is how I did it:
Created a "template geometry" with ExtrudeGeometry using a dummy
10 sided polygon.
As before, created a bunch of "points", this time assigning each
point one of these template geometries.
On each frame, iterated through each geometry, updating each vertex
to that of the new one (using the voronoi alg as before).
If there are extra vertices left over, "bunch" them up into a single point. (see http://github.com/mrdoob/three.js/wiki/Updates.)
Looking at it now, it's quite a simple process. Before, the thought of manipulating each vertex seemed otherworldly to me, but it's not actually too tricky with simple shapes!
Here's how I did the iteration, polycColumn is just a 2 item array with the same polygon in each item:
// Set the vertex index
var v = 0;
// Iterate over both top and bottom of poly
for (var p=0;p<polyColumn.length;p++) {
// Iterate over half the vertices
for (var j=0;j<verts.length/2;j++) {
// create correct z-index depending on top/bottom
if (p == 1) {
var z = point.extrudeAmount;
} else {
var z = 0;
}
// If there are still legitimate verts
if (j < poly.length) {
verts[v].x = poly[j][0];
verts[v].y = poly[j][1];
verts[v].z = z;
// If we've got extra verts, bunch them up in the same place
} else {
verts[v].x = verts[v - 1].x;
verts[v].y = verts[v - 1].y;
verts[v].z = z;
}
v++;
}
}
point.mesh.geometry.verticesNeedUpdate = true;
I'm trying to create a vertex animation for a mesh.
Just imagine a vertex shader, but in software instead of hardware.
Basically what I do is to apply a transformation matrix to each vertex. The mesh it's ok but the normals doesn't look good at all.
I've try to use both computeVertexNormals() and computeFaceNormals() but it just doesn't work.
The following code is the one I used for the animation (initialVertices are the initial vertices generated by the CubeGeometry):
for (var i=0;i<mesh1.geometry.vertices.length; i++)
{
var vtx=initialVertices[i].clone();
var dist = vtx.y;
var rot=clock.getElapsedTime() - dist*0.02;
matrix.makeRotationY(rot);
vtx.applyMatrix4(matrix);
mesh1.geometry.vertices[i]=vtx;
}
mesh1.geometry.verticesNeedUpdate = true;
Here there're two examples, one working correctly with CanvasRenderer:
http://kile.stravaganza.org/lab/js/dynamic/canvas.html
and the one that doesn't works in WebGL:
http://kile.stravaganza.org/lab/js/dynamic/webgl.html
Any idea what I'm missing?
You are missing several things.
(1) You need to set the ambient reflectance of the material. It is reasonable to set it equal to the diffuse reflectance, or color.
var material = new THREE.MeshLambertMaterial( {
color:0xff0000,
ambient:0xff0000
} );
(2) If you are moving vertices, you need to update centroids, face normals, and vertex normals -- in the proper order. See the source code.
mesh1.geometry.computeCentroids();
mesh1.geometry.computeFaceNormals();
mesh1.geometry.computeVertexNormals();
(3) When you are using WebGLRenderer, you need to set the required update flags:
mesh1.geometry.verticesNeedUpdate = true;
mesh1.geometry.normalsNeedUpdate = true;
Tip: is it a good idea to avoid new and clone in tight loops.
three.js r.63
I'm trying to create a 3D terrain using WebGL. I have a jpg with the texture for the terrain, and another jpg with the height values (-1 to 1).
I've looked at various wrapper libraries (like SpiderGL and Three.js), but I can't find a sutable example, and if I do (like in Three.js) the code is not documented and I can't figure out how to do it.
Can anyone give me a good tutorial or example?
There is an example at Three.js http://mrdoob.github.com/three.js/examples/webgl_geometry_terrain.html which is almost what I want. The problem is that they create the colour of the mountains and the height values randomly. I want to read these values from 2 different image files.
Any help would be appriciated.
Thanks
Check out this post over on GitHub:
https://github.com/mrdoob/three.js/issues/1003
The example linked there by florianf helped me to be able to do this.
function getHeightData(img) {
var canvas = document.createElement( 'canvas' );
canvas.width = 128;
canvas.height = 128;
var context = canvas.getContext( '2d' );
var size = 128 * 128, data = new Float32Array( size );
context.drawImage(img,0,0);
for ( var i = 0; i < size; i ++ ) {
data[i] = 0
}
var imgd = context.getImageData(0, 0, 128, 128);
var pix = imgd.data;
var j=0;
for (var i = 0, n = pix.length; i < n; i += (4)) {
var all = pix[i]+pix[i+1]+pix[i+2];
data[j++] = all/30;
}
return data;
}
Demo: http://oos.moxiecode.com/js_webgl/terrain/index.html
Two methods that I can think of:
Create your landscape vertices as a flat grid. Use Vertex Texture Lookups to query your heightmap and modulate the height (probably your Y component) of each point. This would probably be the easiest, but I don't think browser support for it is very good right now. (In fact, I can't find any examples)
Load the image, render it to a canvas, and use that to read back the height values. Build a static mesh based on that. This will probably be faster to render, since the shaders are doing less work. It requires more code to build the mesh, however.
For an example of reading image data, you can check out this SO question.
You may be interested in my blog post on the topic: http://www.pheelicks.com/2014/03/rendering-large-terrains/
I focus on how to efficiently create your terrain geometry such that you get an adequate level of detail in the near field as well as far away.
You can view a demo of the result here: http://felixpalmer.github.io/lod-terrain/ and all the code is up on github: https://github.com/felixpalmer/lod-terrain
To apply a texture to the terrain, you need to do a texture lookup in the fragment shader, mapping the location in space to a position in your texture. E.g.
vec2 st = vPosition.xy / 1024.0;
vec3 color = texture2D(uColorTexture, st)
Depending on your GLSL skills, you can write a GLSL vertex shader, assign the texture to one of your texture channels, and read the value in the vertex shader (I believe you need a modern card to read textures in a vertex shader but that may just be me showing my age :P )
In the vertex shader, translate the z value of the vertex based on the value read from the texture.
Babylon.js makes this extremely easy to implement. You can see an example at:
Heightmap Playground
They've even implemented the Cannon.js physics engine with it, so you can handle collisions: Heightmap with collisions
Note: as of this writing it only works with the cannon.js physics plugin, and friction doesn't work (must be set to 0). Also, make sure you set the location of a mesh/impostor BEFORE you set the physics state, or you'll get weird behavior.