How to apply a texture to THREE.ExtrudeGeometry? - javascript

var arcShape = new THREE.Shape();
arcShape.moveTo( 50, 10 );
arcShape.absarc( 10, 10, 40, 0, Math.PI*2, false );
var map1 = new THREE.ImageUtils.loadTexture( 'moon.jpg' );
var geometry = new THREE.ExtrudeGeometry( arcShape, extrudeSettings );
var new3D = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { map: map1 } ) );
new3D.receiveShadow = true;
obj3Dmassive.add( new3D );
Texture (512x512): http://f3.s.qip.ru/cMfvUhNj.png
Result: http://f3.s.qip.ru/cMfvUhNh.png
How to fill a texture figure?

If you just have a straight extrusion path, you can apply textures to extruded shapes without the need for a custom UV generator. e.g.
var extrudeSettings = {
bevelEnabled: false,
steps: 1,
amount: 20, //extrusion depth, don't define an extrudePath
material:0, //material index of the front and back face
extrudeMaterial : 1 //material index of the side faces
};
var geometry = shape.extrude(extrudeSettings);
var mesh = new THREE.Mesh(geometry,
new THREE.MeshFaceMaterial([materialFace, materialSide]));
This is handy for cookie-cutter type shapes.

EDIT: Answer outdated. See Extruding multiple polygons with multiple holes and texturing the combined shape instead.
You are lucky. What you are trying to do has been done in the following example:
https://github.com/mrdoob/three.js/blob/master/examples/webgl_geometry_extrude_uvs2.html
You have to specify your own UV generator function. This example shows you how to do that.
Remember, this is just an example. It may not be correct -- or easy to implement in your case.

Related

How to update geometry of mesh which divides it into two equal parts and also both part have different materials?

I have loaded .obj using OBJLoader2 and also with its .mtl , now when user click on one of Mesh, then i want to change mesh geometry such like that it divides into two equal parts and also have different material for them.
//this.currentobj represents the user clicked mesh.
let geometry = this.currentobj.geometry;
geometry.clearGroups();
geometry.addGroup( 0, Infinity, 0 );
geometry.addGroup( 0, Infinity, 1 );
geometry.addGroup( 0, Infinity, 2 );
geometry.addGroup( 0, Infinity, 3 );
let material0 = new THREE.MeshBasicMaterial({color: 0xff0000});
let material1 = new THREE.MeshBasicMaterial({color: 0x444444});
let material2 = new THREE.MeshBasicMaterial({color: 0x111111});
let material3 = new THREE.MeshBasicMaterial({color: 0x555555});
var materials = [ material0, material1, material2, material3 ];
let mesh = new THREE.Mesh(geometry, materials);
this.scene.add(mesh);
Dividing mesh is a solved problem in three.js. It was recently revised with a new implementation in Jun by #Manthrax and mrdoob requested it be pulled to main as the original csg solution had issues, as per this thread: https://discourse.threejs.org/t/looking-for-updated-plug-in-for-csg/6785/8
I do not know the current status of main, but Manthrax's library is available here: https://github.com/manthrax/THREE-CSGMesh with example code.
The operation returns the resulting mesh collection and the material objects can be modified individually. My own tangentially related question was answered here by Manthrax in April: Threecsg flat sides when expecting volumetric result It shows two different materials on the resulting cut of a sphere and a cube.
For example:
function doCSG(a,b,op,mat){
var bspA = CSG.fromMesh( a );
var bspB = CSG.fromMesh( b );
var bspC = bspA[op]( bspB );
var result = CSG.toMesh( bspC, a.matrix );
result.material = mat;
result.castShadow = result.receiveShadow = true;
return result;
}
var meshA = new THREE.Mesh(new THREE.BoxGeometry(1,1,1));
var meshB = new THREE.Mesh(new THREE.BoxGeometry(1,1,1));
meshB.position.add(new THREE.Vector3( 0.5, 0.5, 0.5);
var meshC = doCSG( meshA,meshB, 'subtract',meshA.material);
console.log(meshC.material);//mesh C result has it's own material derived from meshA but can be new Material.
In your case you'd want to use the bounding box helper to produce a mesh that you move half way into the object and then use that to cut your geometry in half.

Align BoxGeometry between two 3D Points

I make a game where you can add objects to a world without using a grid. Now I want to make a footpath. When you click on "Create footpath", then you can add a point to on the world at the raycaster position. After you add a first point you can add a second point to the world. When these 2 objects where placed. A line/footpath is visible from the first point to the second one.
I can do this really simple with THREE.Line. See the code:
var lineGeometry = new THREE.Geometry();
lineGeometry.vertices.push( new THREE.Vector3(x1,0,z1), new THREE.Vector3(x2,0,z2) );
lineGeometry.computeLineDistances();
var lineMaterial = new THREE.LineBasicMaterial( { color: 0xFF0000 } );
var line = new THREE.Line( lineGeometry, lineMaterial );
scene.add(line);
But I can't add a texture on a simple line. Now I want to do something the same with a Mesh. I have the position of the first point and the raycaster position of the second point. I also have the lenght between the two objects for the lenght of the footpath. But I don't know how I can get the rotation what is needed.
Note. I saw something about LookAt, is this maybe a good idea, how can I use this with a mesh?
Can anyone help me to get the correct rotation for the footpath object?
I use this code for the foodpath mesh:
var loader = new THREE.TextureLoader();
loader.load('images/floor.jpg', function ( texture ) {
var geometry = new THREE.BoxGeometry(10, 0, 2);
var material = new THREE.MeshBasicMaterial({ map: texture, overdraw: 0.5 });
var footpath = new THREE.Mesh(geometry, material);
footpath.position.copy(point2);
var direction = // What can I do here?
footpath.rotation.y = direction;
scene.add(footpath);
});
I want to get the correct rotation for direction.
[UPDATE]
The code of WestLangley helps a lot. But it works not in all directions. I used this code for the lenght:
var lenght = footpaths[i].position.z - point2.position.z;
What can I do that the lenght works in all directions?
You want to align a box between two 3D points. You can do that like so:
var geometry = new THREE.BoxGeometry( width, height, length ); // align length with z-axis
geometry.translate( 0, 0, length / 2 ); // so one end is at the origin
...
var footpath = new THREE.Mesh( geometry, material );
footpath.position.copy( point1 );
footpath.lookAt( point2 );
three.js r.84

THREE.js Geometry map does not appear

Following I'm loading a image map on a custom geometry,
it represents the brown colored geometry on the picture above:
var aqua_ground_geo = new THREE.Geometry();
var top0 = new THREE.Vector3(aqua_ground_geo_x_NEG, user_data['aqua_soil_calc_b_y'], aqua_ground_geo_z_NEG);
var top1 = new THREE.Vector3(aqua_ground_geo_x_POS, user_data['aqua_soil_calc_b_y'], aqua_ground_geo_z_NEG);
var top2 = new THREE.Vector3(aqua_ground_geo_x_NEG, user_data['aqua_soil_calc_f_y'], aqua_ground_geo_z_POS);
aqua_ground_geo.vertices.push(top0);
aqua_ground_geo.vertices.push(top1);
aqua_ground_geo.vertices.push(top2);
aqua_ground_geo.faces.push( new THREE.Face3(0,1,2) );
aqua_ground_geo.computeFaceNormals();
aqua_ground_geo.computeVertexNormals();
var textureUrl = "http://www.lifeguider.de/wp-content/uploads/aquag/bodengrund/dennerle_kies_naturweiss_1-2mm.jpg";
var aqua_bodengrund_tex = new THREE.TextureLoader().load( textureUrl );
var aqua_bodengrund_mat = new THREE.MeshLambertMaterial( {
map: aqua_bodengrund_tex,
color: 0xffffff,
} );
aqua_bodengrund_mat.shading = THREE.FlatShading;
aqua_bodengrund_mat.side = THREE.DoubleSide;
var aqua_bodengrund = new THREE.Mesh( aqua_ground_geo,aqua_bodengrund_mat);
On a simple THREE.BoxGeometry all works as expected with the same material (it represents the cube in the picture above):
var lala = new THREE.BoxGeometry( 100, 100, 100 );
var lala2 = new THREE.Mesh( lala,aqua_bodengrund_mat);
I'm not an expert in 3D, what is missing in my code that the image texture will be shown correctly?
You need to apply the texture in the callback of the THREE.TextureLoader. Check also the documentation here; the second argument (onLoad) is the callback.
var textureUrl = "https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/crate.gif";
var aqua_bodengrund_mat = new THREE.MeshLambertMaterial( {
color: 0xffffff
});
var onLoad = function( texture ){
aqua_bodengrund_mat.map = texture;
aqua_bodengrund_mat.needsUpdate = true;
}
var loader = new THREE.TextureLoader();
loader.load( textureUrl, onLoad );
See this fiddle for a demo.
UPDATE
In case you have a custom geometry you also need to calculate the UVs for showing the texture correctly. I used this answer here to calculate them in another fiddle here
Note. The UVs in my fiddle are calculated for faces in the XY plane, if your faces are in another plane you will have to update accordingly...

Threejs BufferGeometry - Render some faces with other Texture

i have an output date like this:
geom[0] = {
texturesindexT: new Int16Array([0,1,2,3]),
texturesindexS: new Int16Array([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,...]),
materialsindexT: new Int16Array([-1,-1,-1,-1]),
materialsindexS: new Int16Array([-1,0,1,2,3,4,5,0,6,2,7,8,-1,0,...]),
startIndicesT: new Uint32Array([0,288,606,897,1380]),
startIndicesS: new Uint32Array([1380,1431,1479,1485,1497,1515,1659,...]),
m_indices: new Uint16Array([0,1,2,3,0,2,4,2,5,4,6,2,7,3,2,8,9,10,...]),
m_vertices: new Float32Array([-81.93996,25.7185,-85.53822,-81.93996,...]),
m_normals: new Float32Array([-0.004215205,0.9999894,-0.001817489,-0.004215205,...]),
m_texCoords: new Float32Array([0,0.04391319,0,0.2671326,0.009521127,0.03514284,...]),
}
var textures = new Array("-1_-1/t0.jpg","-1_-1/t1.jpg","-1_-1/t2.jpg",...);
The Data is in order for an Index, Vertex and Normal-Buffer but sections have to be rendered with other Textures and Maretials.
I have tried to make a THREE.Geometry out of the indices, vertices and texCoords/UVCoords but that didn't work.
Now i am trying use a THREE.BufferGeometry() and this work BUT i need to render index 0 to 287 with Texture "textures[0]" and index 288 to 605 with "textures[1]" and so on.
My first attempt was to make a BufferGeometry for each part with index 288 to 605 , but since the Indices are in order for the hole model, i have to put the complete vertices, normales and UVCoords in the Buffer for just a couple of faces.
Is there a way to render sections of the BufferGeometry with other Textures or to set the Texture Index for each Face?
Or is it possible to create a Material, that renders the first X faces with Texture A and the next with Texture B???
If you want to use two different textures with a single BufferGeometry, you can use this pattern, which sets drawcalls:
var geometry1 = new THREE.BufferGeometry();
// ...and set the data...
var geometry2 = geometry1.clone();
// set drawcalls
geometry1.offsets = geometry1.drawcalls = []; // currently required
geometry1.addDrawCall( start1, count1, 0 );
geometry2.offsets = geometry2.drawcalls = []; // currently required
geometry2.addDrawCall( start2, count2, 0 );
var material1 = new THREE.MeshPhongMaterial( { map: map1 } );
var material2 = new THREE.MeshPhongMaterial( { map: map2 } );
var mesh1 = new THREE.Mesh( geometry1, material1 );
var mesh2 = new THREE.Mesh( geometry2, material2 );
three.js r.70
You can create two geometries with same vertex buffers and different indexes:
var position = new THREE.BufferAttribute(positionArray, 3);
var normal = new THREE.BufferAttribute(normalArray, 3);
var uv = new THREE.BufferAttribute(uvArray, 2);
var indices1 = new THREE.BufferAttribute(indexArray1, 1);
var geometry1 = new THREE.BufferGeometry();
geometry1.addAttribute('position', position);
geometry1.addAttribute('normal', normal);
geometry1.addAttribute('uv', uv);
geometry1.addAttribute('index', indices1);
var indices2 = new THREE.BufferAttribute(indexArray2, 1);
var geometry2 = new THREE.BufferGeometry();
geometry2.addAttribute('position', position);
geometry2.addAttribute('normal', normal);
geometry2.addAttribute('uv', uv);
geometry2.addAttribute('index', indices2);
and then create 2 meshes with different materials as you normally would. As far as I understand, this will re-use same data in both meshes.

How can I bind two shapes together as one in Three.js?

Can I bind two different shapes together as one shape?
For example, binding sphere and cylinder together as one?
Kind of, yes. There are multiple options:
via hierarchy you can simply add one mesh to another using the add() function
via the GeometryUtil's merge() function to merge vertices and meshes of two Geometry objects into one
using a basic 3D editor that supports Boolean operations between meshes and exporting.
Method 1 is pretty straightforward:
var sphere = new THREE.Mesh(new THREE.SphereGeometry(100, 16, 12), new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading }));
var cylinder = new THREE.Mesh(new THREE.CylinderGeometry(100, 100, 200, 16, 4, false), new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading } ));
cylinder.position.y = -100;
scene.add(sphere);
scene.add(cylinder);
Notice that 16 is repeated, so the subdivisions level in one mesh matches the other (for a decent look).
Method 2.1 - via GeometryUtils
// Make a sphere
var sg = new THREE.SphereGeometry(100, 16, 12);
// Make a cylinder - ideally the segmentation would be similar to predictable results
var cg = new THREE.CylinderGeometry(100, 100, 200, 16, 4, false);
// Move vertices down for cylinder, so it maches half the sphere - offset pivot
for(var i = 0 ; i < cg.vertices.length; i++)
cg.vertices[i].position.y -= 100;
// Merge meshes
THREE.GeometryUtils.merge(sg, cg);
var mesh = new THREE.Mesh(sg, new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading }));
scene.add(mesh);
Method 2.2 merging a Lathe half-sphere and a cylinder:
var pts = []; // Points array
var detail = .1; // Half-circle detail - how many angle increments will be used to generate points
var radius = 100; // Radius for half_sphere
var total = Math.PI * .51;
for(var angle = 0.0; angle < total ; angle+= detail) // Loop from 0.0 radians to PI (0 - 180 degrees)
pts.push(new THREE.Vector3(0,Math.cos(angle) * radius,Math.sin(angle) * radius)); // Angle/radius to x,z
var lathe = new THREE.LatheGeometry(pts, 16); // Create the lathe with 12 radial repetitions of the profile
// Rotate vertices in lathe geometry by 90 degrees
var rx90 = new THREE.Matrix4();
rx90.setRotationFromEuler(new THREE.Vector3(-Math.PI * .5, 0, 0));
lathe.applyMatrix(rx90);
// Make cylinder - ideally the segmentation would be similar for predictable results
var cg = new THREE.CylinderGeometry(100, 100, 200, 16, 4, false);
// Move vertices down for cylinder, so it maches half the sphere
for(var i = 0 ; i < cg.vertices.length; i++)
cg.vertices[i].position.y -= 100;
// Merge meshes
THREE.GeometryUtils.merge(lathe, cg);
var mesh = new THREE.Mesh(lathe, new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading}));
mesh.position.y = 150;
scene.add(mesh);
The one problem I can't address at the moment comes from the faces that are inside the mesh. Ideally, those would have normals flipped, so they wouldn't render, but I haven't found a quick solution for that.
The third is fairly straightforward. Most 3D packages allow Boolean operation on meshes (e.g., merging two meshes together with the ADD operation (meshA + meshB)). Try creating a cylinder and a sphere in Blender (free and opensource), which already has a Three.js exporter. Alternatively you can export an .obj file of the merged meshes from your 3D editor or choice and use the convert_obj_three script.
I've found yet another method, which might be easier/more intuitive. Remember the Boolean operations I've mentioned above?
It turns out there is an awesome JavaScript library just for that: Constructive Solid Geometry:
Chandler Prall wrote some handy functions to connect CSG with three.js. So with the CSG library and the Three.js wrapper for it, you can simply do this:
var cylinder = THREE.CSG.toCSG(new THREE.CylinderGeometry(100, 100, 200, 16, 4, false), new THREE.Vector3(0, -100, 0));
var sphere = THREE.CSG.toCSG(new THREE.SphereGeometry(100, 16, 12));
var geometry = cylinder.union(sphere);
var mesh = new THREE.Mesh(THREE.CSG.fromCSG(geometry), new THREE.MeshNormalMaterial());
Which gives you a nice result (no problems with extra faces/flipping normals, etc.):

Categories