need some help setting up UV's for THREE.js geometry. I made myself a sphere shape out of custom places points in 3D space. I know its easier to just use SphereGeometry and be done with it but I want to learn how to use custom geometry. I currently manage to apply the texture, and it sort of looks good until I noticed this line of texture:
image
At first I thought there was a mistake when generating points for geometry, but after some tweaking I cut down the problem to my UV assignment function:
function assignUVs(geometry, r) {
function cartesian2polar(position){
return({
phi:Math.acos(position.y / r),
theta:Math.atan2(position.z, position.x)
});
}
function polar2canvas(polarPoint) {
return({
y: polarPoint.phi/Math.PI,
x: (polarPoint.theta+Math.PI) / (2*Math.PI)
})
}
var polarVertices = geometry.vertices.map(cartesian2polar);
geometry.faceVertexUvs[0] = [];
geometry.faces.forEach(function(face) {
var uvs = [];
var ids = [ 'a', 'b', 'c'];
for( var i = 0; i < ids.length; i++ ) {
var vertexIndex = face[ ids[ i ] ];
var vertex = polarVertices[ vertexIndex ];
if(vertex.theta === 0 && (vertex.phi === 0 || vertex.phi === Math.PI)) {
var alignedVertice = vertex.phi === 0 ? face.b : face.a;
vertex = {
phi: vertex.phi,
theta: polarVertices[ alignedVertice ].theta
};
}
if(vertex.theta === Math.PI && cartesian2polar(face.normal).theta < Math.PI/2) {
vertex.theta = -Math.PI;
}
var canvasPoint = polar2canvas(vertex);
uvs.push( new THREE.Vector2( 1.0-canvasPoint.x, 1.0-canvasPoint.y ) );
geometry.faceVertexUvs[ 0 ].push( uvs );
});
geometry.uvsNeedUpdate = true;
}
So my question is, how to I get rid of this strip of texture?
Any help would be appreciated.
Related
I'm working on adapting the object loading example on the THREE.js example page to allow for the loaded objects to have their faces selected and colored. My general strategy has been to follow the steps outlined here.
However, since I am not using THREE.geometry objects, I am having trouble piecing together this strategy with the obj loader code.
I currently have ray intersection working so I know when I am clicking on the object, I am just having trouble coloring the faces. I know I need to apply: vertexColors: THREE.FaceColors to the obj material and thats where I'm stuck.
My current obj loading code is as follows:
var loader = new THREE.OBJLoader( manager );
loader.load( path, function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
var faceColorMaterial = new THREE.MeshBasicMaterial({ color: 0xff00ff, vertexColors: THREE.VertexColors } );
child.material = faceColorMaterial;
child.geometry.addAttribute( "color", new THREE.BufferAttribute( new Float32Array( 3 * 3 ), 3 ) );
child.geometry.dynamic = true;
}
} );
scene.add( object );
targetList.push(object);
}, onProgress, onError );
And my detection and coloring code:
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
vector.unproject( camera );
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersects = ray.intersectObjects( targetList, true );
if ( intersects.length > 0 ) {
var colorArray = intersects[0].object.geometry.attributes.color.array;
var face = intersects[0].face;
colorArray[face.a] = 1;
colorArray[face.a + 1] = 0;
colorArray[face.a + 2] = 0;
colorArray[face.b] = 1;
colorArray[face.b + 1] = 0;
colorArray[face.b + 2] = 0;
colorArray[face.c] = 1;
colorArray[face.c + 1] = 0;
colorArray[face.c + 2] = 0;
intersects[0].object.geometry.attributes.color.needsUpdate = true;
}
When I run this, my object is black and when I click the faces, they do not change color.
How can I alter my code to allow for the changes to be colored when selected?
OBJLoader should be returning BufferGeometry objects, which don't use FaceColors. BufferGeometry can use VertexColors to assign colors to a face, but you need to ensure the colors attribute is applied to the BufferGeometry. If you do have a colors attribute, then you're in luck, because OBJLoader gives you unique vertices for each triangle.
When intersectObjects returns, the Face3 should have a, b, and c, which I believe should be indicies into the mesh.geometry.attributes.position.array, but they'll also be indices into the mesh.geometry.attributes.color.array array.
var colorArray = intersects[0].object.geometry.attributes.color.array,
face = intersects[0].face;
colorArray[face.a] = 1;
colorArray[face.a + 1] = 0;
colorArray[face.a + 2] = 0;
colorArray[face.b] = 1;
colorArray[face.b + 1] = 0;
colorArray[face.b + 2] = 0;
colorArray[face.c] = 1;
colorArray[face.c + 1] = 0;
colorArray[face.c + 2] = 0;
intersects[0].object.geometry.attributes.color.needsUpdate = true;
This should turn a particular face red. But I can't stress enough that this will only work if your geometry has a colors attribute, and your material is using THREE.VertexColors.
I have made a collision detection. You can place objects at the raycaster/mouse position on a floor. Therefore you need to click on a button 'Add object', then you get a object(helper) that follows the mouse to see if the new object get a collision with another object. When you click on the position you want, the new object will be placed to the world if there is no collision.
The collision detection I have made works perfectly when the object that is already placed in the world has the same size as the helper/new object.
On the next screenshot you can see a big object and a small(red) helper. The color red means that there is a collision. When I move the mouse more to the right it turns green.
Why does my collision detection work only with 2 objects that have the same size and why doesn't it not with different ones?
Here is my code in the Click event to show the big object:
var geometry = new THREE.BoxGeometry(200, 200, 300);
var bigobject = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
color: 0xFBF5D7,
opacity: 1
}));
bigobject.position.copy(intersects[0].point);
bigobject.position.y = 100;
objects.push(bigobject);
scene.add(bigobject);
Here is my code to show the helper when the button 'Add object' is clicked:
var geometry = new THREE.BoxGeometry( 50, 50, 100 );
helper = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: 0x00ff00, opacity: 1 } ) );
helper.name = 'helper';
scene.add( helper );
Here is my code in the MouseMove event to detect the collision:
if(scene.getObjectByName( 'helper' )) {
helper.position.copy( intersects[ 0 ].point );
helper.position.y = 25;
var helperWidth = helper.geometry.parameters.width;
var helperLength = helper.geometry.parameters.depth;
var validpositionObject = true;
for (var i = 0; i < objects.length; i++) {
var objectWidth = objects[i].geometry.parameters.width;
var objectLength = objects[i].geometry.parameters.depth;
// MIN X
var helperMinX = helper.position.x;
var objectMinX = objects[i].position.x;
// MAX X
var helperMaxX = helperWidth + helper.position.x;
var objectMaxX = objectWidth + objects[i].position.x;
// MIN Z
var helperMinZ = helper.position.z;
var objectMinZ = objects[i].position.z;
// MAX Z
var helperMaxZ = helperLength + helper.position.z;
var objectMaxZ = objectLength + objects[i].position.z;
if (objectMinX <= helperMaxX && objectMaxX >= helperMinX && objectMinZ <= helperMaxZ && objectMaxZ >= helperMinZ) {
validpositionObject = false;
}
}
if ( validpositionObject === true ) {
helper.material.color.setHex( 0x00ff00 );
validposition = true;
}else{
helper.material.color.setHex( 0xff0000 );
validposition = false;
}
}
What goes wrong with the position when it is a big and a small object.. Can anyone help me in the right direction? Many thanks
Use this:
function collisonXZ(o1, o2) {
if (Math.abs(o1.position.x - o2.position.x) > (o1.geometry.parameters.width + o2.geometry.parameters.width) / 2)
return false;
if (Math.abs(o1.position.z - o2.position.z) > (o1.geometry.parameters.depth + o2.geometry.parameters.depth) / 2)
return false;
return true;
}
var validpositionObject = true;
for (var i = 0; i < objects.length; i++) {
if (collisionXZ(helper, objects[i])) {
validpositionObject = false;
break;
}
}
I want to extrude faces from a THREE.Geometry object, so my approach was to:
- specify the faces to extrude
- extract vertices on the outer edges
- Draw a THREE.Shape with those vertices
- Extrude it using THREE.ExtrudeGeometry (I'm actually using a modified version which is not generating front faces, as I'm cloning them by myself to keep original topology).
It seems to work having a CircleGeometry on a XY plane, so extruding on Z direction.
but if I rotate the circle a bit this is what I get. It extrudes on Z World Axes, not following its local orientation
I can I solve this? Here's the function I'm using:
function faceExtrude( geometry, face_group, amount ) {
if ( geometry.type === "undefined" ) {
console.log( "faceextrude error" );
return;
}
//convert face_group to vertex_group
var vertex_group = [];
for ( var i = 0; i < face_group.length; i++ ) {
//get vertices ID
var f = face_group[i];
var a = f.a;
var b = f.b;
var c = f.c;
//clone vertices
var v1 = geometry.vertices[a].clone();
var v2 = geometry.vertices[b].clone();
var v3 = geometry.vertices[c].clone();
//add them to an array
vertex_group.push(v1, v2, v3);
}
// Side Faces
var vertsToExtrude = [];
//Get only verts in outer edge
for ( var v = 0; v < vertex_group.length; v++ ) {
var shareCount = 0;
//count their presence in each face
for ( var f = 0; f < face_group.length; f++ ) {
if( v == face_group[ f ].a ||
v == face_group[ f ].b ||
v == face_group[ f ].c )
{
shareCount++;
}
}
//if < 4 than it's in an outer edge. Add it to an array
if ( shareCount < 4) {
vertsToExtrude.push( vertex_group[ v ].clone() );
}
}
//make a shape with them
var shape = new THREE.Shape();
shape.moveTo( vertsToExtrude[ 0 ].x, vertsToExtrude[ 0 ].y );
for ( var i = 1; i < vertsToExtrude.length; i++ ){
shape.lineTo( vertsToExtrude[ i ].x, vertsToExtrude[ i ].y );
}
shape.lineTo( vertsToExtrude[ 0 ].x, vertsToExtrude[ 0 ].y );
//extrude it
var extrudeSettings = { amount: amount, steps: 2, bevelEnabled: false };
var sideGeo = new THREE.MyExtrudeGeometry( shape, extrudeSettings );
//Front Faces
var frontGeo = new THREE.Geometry();
for ( var i = 0; i < face_group.length; i++ ) {
//get vertices ID
var f = face_group[i];
var a = f.a;
var b = f.b;
var c = f.c;
//new vertices IDs
var new_a = frontGeo.vertices.length;
var new_b = frontGeo.vertices.length + 1;
var new_c = frontGeo.vertices.length + 2;
//clone vertices
var v1 = geometry.vertices[a].clone();
var v2 = geometry.vertices[b].clone();
var v3 = geometry.vertices[c].clone();
frontGeo.vertices.push(v1, v2, v3);
//translate them depending on "amount"
v1.z = v1.z + amount;
v2.z = v2.z + amount;
v3.z = v3.z + amount;
//add them to a face
var f = new THREE.Face3(new_a, new_b, new_c);
frontGeo.faces.push(f);
}
frontGeo.computeFaceNormals();
geometry.merge( sideGeo );
geometry.merge( frontGeo );
};
I'm blind. Missed this while reading docs about ExtrudeGeometry:
"extrudePath — THREE.CurvePath. 3d spline path to extrude shape along. (creates Frames if (frames aren't defined)"
Think this is the solution I'm looking for :)
I am want to draw a plane that has an an array of points lying on it (including the origin). The three.js library draws the plane on the origin, facing the xy plane. Right now, I am having trouble of moving the it from the origin to a position such that it contains the points.
So far, I have managed to find a way to orient some planes that lie on the y-axis:
var directionalVectors = __getDirectionVectors(points);
var normal = __getNormalOfPlane(directionalVectors);
var angleXY = __getAngleBetweenPlanes( normal, new THREE.Vector3(0, 0, 1) );
plane.rotateY(- angleXY );
plane.translateY( planeDimensions.width /2.0);
plane.translateX( planeDimensions.height /2.0);
This is how I calculate the direction vectors:
var __getDirectionVectors = function( points ){
var numOfPoints = points.length, i;
var pointOne, pointTwo, directionalVectors = [], directionalVector;
for( i = 0; i < numOfPoints - 1; i++){
pointOne = points[i];
pointTwo = points[i + 1];
directionalVector = new THREE.Vector3().subVectors(pointOne, pointTwo);
directionalVectors.push(directionalVector);
}
return directionalVectors;
};
This is how I calculate the normal:
var __getNormalOfPlane = function(vectors){
var numOfVectors = vectors.length;
var vectorOne, vectorTwo, normal;
if( numOfVectors >= 2){
vectorOne = vectors[0];
vectorTwo = vectors[1];
normal = new THREE.Vector3().crossVectors(vectorOne, vectorTwo);
}
return normal;
};
This is how I calculate the angle between the plane and the XY plane:
//http://www.netcomuk.co.uk/~jenolive/vect14.html
var __getAngleBetweenPlanes = function( normalOne, normalTwo){
var dotPdt = normalOne.dot(normalTwo);
var angle = Math.acos( dotPdt / ( normalOne.length() * normalTwo.length() ) );
return angle;
}
Is there any way I could orient the plane properly for all types of planes?
As quads faces are no longer supported in three.js. I am having trouble Raycast picking segments between 4 vertices points as a square as they are now made up of triangle faces.
So my picking returns triangle shape not square:
var faces = 10;
var myplane = new THREE.Mesh(
new THREE.PlaneGeometry(1000, 1000, faces, faces),
new THREE.MeshNormalMaterial({
color: 0x993333,
wireframe: false
}));
objects.push(myplane);
scene.add(myplane);
//raycast code
if ( intersects.length > 0 ) {
_controls.noRotate = true;
var face = intersects[0].face;
var temp = intersects[0].object;
var geo = temp.geometry;
geo.vertices[face.a].z -= 20;
geo.vertices[face.b].z -= 20;
geo.vertices[face.c].z -= 20;
//want other face verts below basically
geo.vertices[face.d].z -= 20;
geo.vertices[face.e].z -= 20;
geo.vertices[face.f].z -= 20;
geo.verticesNeedUpdate = true;
if(intersects[ 0 ].faceIndex % 2 == 0){
// geo.vertices[intersects[0].faceIndex + 1].z = 40;
// console.log("Even face Index: " + intersects[ 0 ].faceIndex);
}else{
// console.log("Odd face Index: " + intersects[ 0 ].faceIndex -1);
//geo.vertices[intersects[0].faceIndex - 1].z = 40, of course doest work
}
}
Below is a 2d example for a better vsual...
Currently I get the red out-come , but I'm wanting to pick like the green example:
try this code to get intersection faces
if ( intersects.length > 0 )
{
var faceIndex = intersects[0].faceIndex;
var obj = intersects[0].object;
var geom = obj.geometry;
var faces = obj.geometry.faces;
var facesIndices = ["a","b","c"];
facesIndices.forEach(function(indices){
geom.vertices[faces[faceIndex][indices]].setZ(-10);
});
if(faceIndex%2 == 0){
faceIndex = faceIndex+1;
}else{
faceIndex = faceIndex-1;
}
facesIndices.forEach(function(indices){
geom.vertices[faces[faceIndex][indices]].setZ(-10);
});
geom.verticesNeedUpdate = true;
}
In the general case, getting and changing the geometry of a raycast to mesh triangle intersection is just:
var face = intersects[0].face;
var obj = intersects[0].object;
var geom = obj.geometry;
var vertA = geom.vertices[face.a];
var vertB = geom.vertices[face.b];
var vertC = geom.vertices[face.c];
vertA.setY(100);
vertB.setY(110);
vertC.setY(105);
geom.verticesNeedUpdate = true;
No need to iterate through the faces array, or build a contrived face vertex array just to loop through it in the next step.