THREE.JS UV Mapping on ExtrudeGeometry - three.js

I need to apply a texture on a ExtrudeGeometry object.
The shape is a circle and the extrude path is composed of 2 vectors :
One for the top.
One for the bottom.
I didn't choose cylinderGeometry because I need to place top/bottom sections of my geometry at precise positions and because the geometry created will not be always purely vertical (like a oblique cylinder for example).
Here is a picture of a section (one top vector, one bottom vector and a shape extruded between these 2 vectors).
and a picture of the texture I'm trying to apply.
All I want to do is to wrap this picture on the vertical sides of my object just one time.
Here is my code :
var biVectors = [ new THREE.Vector3( this.startVector.x, this.startVector.y, this.startVector.z ) , new THREE.Vector3( this.endVector.x, this.endVector.y, this.endVector.z ) ];
var wellSpline = new THREE.SplineCurve3(biVectors);
var extrudeSettings = {
steps : 1,
material: 0,
extrudeMaterial: 1,
extrudePath : wellSpline
};
var pts = [];
for (var i = 0; i <= this.segments; i++) {
var theta = (i / this.segments) * Math.PI * 2;
pts.push( new THREE.Vector3(Math.cos(theta) * this.diameter , Math.sin(theta) * this.diameter, 0) );
}
var shape = new THREE.Shape( pts );
var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
var texture = THREE.ImageUtils.loadTexture( 'textures/sampleTexture2.jpg' );
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.flipY = false;
var material = new THREE.MeshBasicMaterial( { map: texture } );
var slice = new THREE.Mesh( geometry, material );
var faceNormals = new THREE.FaceNormalsHelper( slice );
console.log("face normals: ", faceNormals);
myCanvas.scene.add( faceNormals );
slice.parentObject = this;
myCanvas.scene.add( slice );
this.object3D = slice;
}
Now, as you can see, the mapping is not correct at all.
I've read a lot of information about this problem the last 3 days. But I'm running out of options as I'm new to THREE.JS.
I think I have to redefine the UV coordinates but I have no clue how to do this.
It seems that wrapping a texture on a cylinder like object is anything but easy in THREE.JS.
Can someone please help me on this issue ?

Related

Get center position of hole in ExtrudeGeometry

I trying to get position of hole in extruded geometry. I created plane and made hole in her geometry. I want get x,y,z coordinates in center of hole. Is there some methods to get it?
Here demo: https://codepen.io/DYDOI-NSK/pen/XWqJzXG?editors=0011
Here code:
I created shape of plane
let shape = new THREE.Shape();
let width = 30;
let height = 30;
shape.moveTo(-width, height);
shape.lineTo(-width, -height);
shape.lineTo(width, -height);
shape.lineTo(width, height);
shape.lineTo(-width, height);
I created hole path and add it to shape
let hole = new THREE.Path();
hole.absarc(20, 10, 10, 0, Math.PI * 2, false) //first two argumets is x,y coord of hole
shape.holes.push(hole)
I created plane add add extruded geometry
let geometry = new THREE.PlaneGeometry( 30, 30);
let material = new THREE.MeshBasicMaterial( {color: new THREE.Color('#cea6a6'), side: THREE.DoubleSide} );
let mesh = new THREE.Mesh( geometry, material );
let newGeometry = new THREE.ExtrudeGeometry(shape, settings);
mesh.geometry.dispose()
mesh.geometry = newGeometry;
After 4 days I understand how can do it. I simple created line from mesh center to hole config position. Applied quaternion to line and got x,y,z cords of hole.
Maybe there are more optimized solutions, but it only that I could create. I will be glad if someone share more optimized solution :D
Here codepen: https://codepen.io/DYDOI-NSK/pen/XWqJzXG?editors=0011
Here code:
/*
* findCoords - function to find hole coords in 3d space
* data - parameter that require x,y of hole
*/
let findCoords = function (data) {
let vertexes = [];
//Set coords where you was created hole
let hole = new THREE.Vector3(
data.x,
data.y,
0
);
vertexes.push( new THREE.Vector3() );
vertexes.push(hole)
//Create line
const material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
const geometry = new THREE.BufferGeometry().setFromPoints( vertexes );
const line = new THREE.Line( geometry, material );
scene.add(line)
//Set line to center of mesh
line.position.copy(mesh.position)
//Rotate line like rotated mesh
line.applyQuaternion(mesh.quaternion)
//Extract hole coords from second vertex of line
let holeCoord = new THREE.Vector3()
const positionAttribute = line.geometry.getAttribute( 'position' );
holeCoord.fromBufferAttribute( positionAttribute, 1);
return holeCoord;
}

THREE.js planeGeometry clipped inside the walls

I want to make a 3D building using Three.js. For example, I made 6 walls and a floor by checkerboard texture. I used clippingPlanes for wall1 and wall4:
floor1.material.clippingPlanes = [plane1,plane4];
I made my planes(plane1 and plane4) by my walls(wall1 and wall4). For example, my wall4 planeGeometry and plane4 code is here:
var wallGeometry4 = new THREE.PlaneGeometry(40, Floor_Height, 1, 1);
var wall4 = createMesh(wallGeometry4, "brick_diffuse.jpg", THREE.DoubleSide, 1024, 1024);
unit1.add(wall4);
wall4.position.x = -10;
wall4.position.y = 0;
wall4.position.z = -20;
wall4.rotation.y = 1.5 * Math.PI;
wall4.add(new THREE.EdgesHelper(wall4, 0x000000));
var plane4 = new THREE.Plane();
var normal4 = new THREE.Vector3();
var point4 = new THREE.Vector3();
normal4.set(0, 0, -1).applyQuaternion(wall4.quaternion);
point4.copy(wall4.position);
plane4.setFromNormalAndCoplanarPoint(normal4, point4);
But I see an empty area between wall5 and wall6, because plane4(that used for clipping the floor) isn't the same size of wall4. I think Plane4 is whole of the scene. How to change size of my plane to clip correctly? Or Is there any way to make floor bounded in walls?
One way to achieve this as suggested is to use ShapeGeometry.
When you are creating your walls you can save the x and z co-ordinate of their starting and ending points in an array to form a loop of points of Vector2. Then you can create a new custom shape from these points using shapeGeometry.
points = [{x:0,y:0},{x:0,y:10},{x:10,y:10},{x:10,y:0},{x:0,y:0}]
function getShapeFromPoints(points){
const shape = new THREE.Shape();
shape.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
shape.lineTo(points[i].x, points[i].y);
}
return shape;
}
function createPlaneFromPoints(points) {
const planeMaterial = getPlaneMaterial();
const shape = getShapeFromPoints(points);
const geometry = new THREE.ShapeBufferGeometry(shape);
geometry.rotateX(degreeToRadians(-90));
const mesh = new THREE.Mesh(geometry, planeMaterial);
return mesh;
}
Hope that helps you!

Merge geometries, but use a single material

Somewhat new to Three.js and 3d libraries in general.
I merged two geometries (a quarter cylinder and a plane) using this code:
var planeGeo = new THREE.PlaneGeometry(planeW, planeD / 2, 199, 399);
var planeMesh = new THREE.Mesh(planeGeo);
planeMesh.updateMatrix();
var cylinderGeo = new THREE.CylinderGeometry(100, 100, planeW, 199, 399, true, 0, Math.PI / 2);
cylinderGeo.rotateZ(Math.PI / 2).translate(0, 200, -100);
var cylinderMesh = new THREE.Mesh(cylinderGeo);
cylinderMesh.updateMatrix();
var singleGeometry = new THREE.Geometry();
singleGeometry.merge(planeMesh.geometry, planeMesh.matrix);
singleGeometry.merge(cylinderMesh.geometry, cylinderMesh.matrix);
var testmaterial = new THREE.MeshPhongMaterial({ color: 0x666666 });
mesh = new THREE.Mesh(singleGeometry, testmaterial);
scene.add(mesh);
I then would like to use a single material (png) over the entire thing. This code doesn't work:
textureLoader.load('data/test.png', function (texture) {
material = new THREE.MeshLambertMaterial({
map: texture
});
});
Later in the block with the merging...
mesh = new THREE.Mesh(singleGeometry, material);
scene.add(mesh);
This results in:
I would like the end result to be a single draped png over the entire merged geometry, but I can't find anything that suggests this is a normal thing to do. Is there a better way to achieve that result than merging geometries? Or am I just looking in the wrong places?
A poor-mans solution to achieve this, using the shape supplied in your post, is the following:
https://jsfiddle.net/87wg5z27/44/
Using code from this answer: https://stackoverflow.com/a/20774922/4977165
It sets the UVs based on the bounding box of the geometry, leaving out the z-coordinate (=0). Thats why the texture is a little bit stretched at the top, you can correct that manually or maybe its sufficent for you.
geometry.computeBoundingBox();
var max = geometry.boundingBox.max,
min = geometry.boundingBox.min;
var offset = new THREE.Vector2(0 - min.x, 0 - min.y);
var range = new THREE.Vector2(max.x - min.x, max.y - min.y);
var faces = geometry.faces;
geometry.faceVertexUvs[0] = [];
for (var i = 0; i < faces.length ; i++) {
var v1 = geometry.vertices[faces[i].a],
v2 = geometry.vertices[faces[i].b],
v3 = geometry.vertices[faces[i].c];
geometry.faceVertexUvs[0].push([
new THREE.Vector2((v1.x + offset.x)/range.x ,(v1.y + offset.y)/range.y),
new THREE.Vector2((v2.x + offset.x)/range.x ,(v2.y + offset.y)/range.y),
new THREE.Vector2((v3.x + offset.x)/range.x ,(v3.y + offset.y)/range.y)
]);
}
geometry.uvsNeedUpdate = true;

Threejs merge with multiple materials applies only one material

I have searched on this and found several examples on stackoverflow, but the answers have not solved my problem.
What I have tried:
First I create the geometry bucket to be used for the group and an array to store my materials.
var totalGeom = new THREE.Geometry();
var materials = [];
I then run through my data (strData) with a for loop and call addMapMarker3d.
for(var i=0; i<strData.length;i++){
addMapMarker3d([strData[i].lat,strData[i].lon], strData[i].time, measureColors[i]);
}
The addMapMarker3d function is as follows:
addMapMarker3d = function(latLng, height, color){
var max = 600;
var dist = 25;
var opacity = (height/max);
var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));
//create the material and add it to the materials array
var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
this.materials.push(material);
//create a mesh from the geometry and material.
var cube = new THREE.Mesh( geometry, material);
//leaf is a simple lat/lng to pixel position converter
var actualMarkerPos = leaf.getPoint(latLng);
//apply the position on a 5000x5000 cartesian plane
var extent = 5000;
cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
cube.position.setY(height/2);
//update the matrix and add the cube to the totalGeom bucket
cube.updateMatrix();
totalGeom.merge( cube.geometry, cube.matrix);
}
After the for loop runs and all the cubes are created:
var mats = new THREE.MeshFaceMaterial(materials)
var total = new THREE.Mesh(totalGeom, mats);
world.scene.add(total);
The question
While the geometry merge functions, and my view port is running at a much improved FPS, all the cubes have exactly the same color and opacity. It appears the merge is using a single material of the 10k I supplied. Is there some way to ensure that the geometry uses the material supplied in the array? Am I doing something incorrect?
If I try this in addMapMarker3d:
totalGeom.merge( cube.geometry, cube.matrix, materials.length-1);
I get "Uncaught TypeError: Cannot read property 'transparent' of undefined" and nothing renders, which I don't understand, because by the examples, each geometry should index to a material in the materials array.
three.js r.70
The following technique uses just one material, but allows you to retain the individual color of each merged object. I don't know if it's possible to retain the individual alpha of each merged object.
http://jsfiddle.net/looshi/nsknn53p/61/
For each mesh, set each of its geometry.faces color :
function makeCube(size, color) {
var geom = new THREE.BoxGeometry(size, size, size);
for (var i = 0; i < geom.faces.length; i++) {
face = geom.faces[i];
face.color.setHex(color);
}
var cube = new THREE.Mesh(geom);
return cube;
}
Then, in the parent geometry you are going to mesh into, set its material vertexColors property.
var parentGeometry = new THREE.Geometry();
var parentMatrial = new THREE.MeshLambertMaterial({
color: 0xffffff,
shading: THREE.SmoothShading,
vertexColors: THREE.VertexColors
});
// in a loop you could create many objects and merge them
for (var i = 0; i < 1000; i++) {
cube = makeCube(size, color);
cube.position.set(x, y, z);
cube.rotation.set(rotation,rotation,rotation);
cube.updateMatrix()
parentGeometry.merge(cube.geometry, cube.matrix);
}
// after you're done creating objects and merging them, add the parent to the scene
parentMesh = new THREE.Mesh(parentGeometry, parentMatrial);
scene.add(parentMesh);
My original question was not answered: Is there some way to ensure that the geometry uses the material supplied in the array?
The answer is yes. Multiple materials can be applied to a single mesh during merge. After pushing the material to the materials array, the merge will utilize the geometry face material index. The merge will apply multiple materials when an array is supplied to new THREE.MeshFaceMaterial([materialsArray]) as the total mesh is created. This solves the mystery of the syntax. Just because you supply an array of materials does not mean that the merge will use each material in an iterative fashion as the objects are merged as of r71. The faces must inform the merge which material in the material array to use.
I am using this for a non rendered scene, and the final obj is exported. If you need render performance, see one of the other answers for some options.
A simple for loop on the face array on the geometry informs the merge which material to apply:
addMapMarker3d = function(latLng, height, color){
var max = 600;
var dist = 25;
var opacity = (height/max);
var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));
//create the material and add it to the materials array
var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
this.materials.push(material);
//set the material index of each face so a merge knows which material to apply
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[i].materialIndex = this.materials.length-1;
}
//create a mesh from the geometry and material.
var cube = new THREE.Mesh( geometry, material);
//leaf is a simple lat/lng to pixel position converter
var actualMarkerPos = leaf.getPoint(latLng);
//apply the position on a 5000x5000 cartesian plane
var extent = 5000;
cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
cube.position.setY(height/2);
//update the matrix and add the cube to the totalGeom bucket
cube.updateMatrix();
totalGeom.merge( cube.geometry, cube.matrix);
}
The only reason you are seeing improved performance is because, after the merge, there is only one material. If you want your scene to have multiple materials you should not merge.
Adding to the same group:
var group = new THREE.Object3D();
group.add (object1);
group.add (object2);
group.add (object3);

Concentric circles texture on RingGeometry

I am trying to create a flat ring in three.js with a concentric circles texture, like Saturn's rings. I cannot manage to do anything but lines that radiate from the center (like a bicycle wheel), no matter what I put in the image. It seems that textures are applied to RingGeometry in a very different fashion than CircleGeometry.
I could easily apply a concentric circles texture to a CircleGeometry, but a ring (with a hole in the middle) is really what I need. Is anybody aware of a way to have textures on rings do something else than radiate?
I did not find a way in Three.js documentation, nor on the web, to do what I want, as it seems that rings are seldom used by anybody...
Thank you
Go here http://jsfiddle.net/theo/VsWb9/ and replace
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
with
geometry = new THREE.TorusGeometry( 100, .5 , 50 ,50);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh( geometry, material );
If you want to change the ring color to say black for instance
change
material = new THREE.MeshNormalMaterial();
to
material = new THREE.MeshBasicMaterial({color:0x000});
Stick any other material changes in that array input argument to the constructor
function THREE.MeshBasicMaterial({arguments here})
I found this for making the geometry. It creates a disk of theataSeegmens triangles
this.RingGeometry = function ( innerRadius, outerRadius, thetaSegments) {
THREE.Geometry.call( this )
innerRadius = innerRadius || 0
outerRadius = outerRadius || 50
thetaSegments = thetaSegments || 8
innerRadius*=Obj.Size*100;
outerRadius*=Obj.Size*100;
var normal = new THREE.Vector3( 0, 0, 1 )
for(var i = 0; i < thetaSegments; i++ ){
var angleLo = (i / thetaSegments) *Math.PI*2
var angleHi = ((i+1) / thetaSegments) *Math.PI*2
var vertex1 = new THREE.Vector3(innerRadius * Math.cos(angleLo), innerRadius * Math.sin(angleLo), 0);
var vertex2 = new THREE.Vector3(outerRadius * Math.cos(angleLo), outerRadius * Math.sin(angleLo), 0);
var vertex3 = new THREE.Vector3(innerRadius * Math.cos(angleHi), innerRadius * Math.sin(angleHi), 0);
var vertex4 = new THREE.Vector3(outerRadius * Math.cos(angleHi), outerRadius * Math.sin(angleHi), 0);
this.vertices.push( vertex1 );
this.vertices.push( vertex2 );
this.vertices.push( vertex3 );
this.vertices.push( vertex4 );
var vertexIdx = i * 4;
// Create the first triangle
var face = new THREE.Face3(vertexIdx + 0, vertexIdx + 1, vertexIdx + 2, normal);
var uvs = []
var uv = new THREE.Vector2(0, 0)
uvs.push(uv)
var uv = new THREE.Vector2(1, 0)
uvs.push(uv)
var uv = new THREE.Vector2(0, 1)
uvs.push(uv)
this.faces.push(face);
this.faceVertexUvs[0].push(uvs);
// Create the second triangle
var face = new THREE.Face3(vertexIdx + 2, vertexIdx + 1, vertexIdx + 3, normal);
var uvs = []
var uv = new THREE.Vector2(0, 1)
uvs.push(uv)
var uv = new THREE.Vector2(1, 0)
uvs.push(uv)
var uv = new THREE.Vector2(1, 1)
uvs.push(uv)
this.faces.push(face);
this.faceVertexUvs[0].push(uvs);
}
//this.computeCentroids();
//this.computeFaceNormals();
this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), outerRadius );
};
this.RingGeometry.prototype = Object.create( THREE.Geometry.prototype );
The above line is important to get it to work.
Here is a sugestion how to set the ring material.assuming that you have two pictures (just a sqare section) that can be used for alphamap and for
the actual ring.
var ringMaterial = new THREE.MeshPhongMaterial(
{
map: SaturnRingColor,
alphaMap:SaturnRingPattern,
color: 0xffffff,
specular: 0x555555,
shininess: 3,
emissive:10,
side: THREE.DoubleSide,
castshadow:true,
transparent : true,
opacity : 0.9,
} );
this.ringMesh = new THREE.Mesh( this.RingGeometry , RingMaterial );

Resources