ThreeJS: How to remove vertices? - javascript

Adding new vertices to a three.js mesh goes by mesh.geometry.vertices.push(new THREE.Vector3(x, y, z)), but how do I remove them?
"geometry" is an array, so I thought, I could remove vertices with:
mesh.geometry.vertices.splice(vertexIndex, 1)
mesh.geometry.verticesNeedUpdate = true;
But when I do that, that whole thing breaks with three.js internal error messages that say: "Uncaught TypeError: Cannot read property 'x' of undefined" inside three.min.js.
I searched their wiki, their github issues. And can't find an answer to this. The mesh is a simple BoxGeometry, so not even a custom one.

In threejs each face is made of 3 vertices. Here is an example to make it clearer. Here is how you create a geometry in r71 :
geometry=new THREE.Geometry();
geometry.vertices.push(// few vertices with random coordinates
new THREE.Vector3(12,15,5),//index:0 -- the numbers are (x,y,z) coordinates
new THREE.Vector3(10,15,5),//index:1
new THREE.Vector3(12,10,2),//index:2
new THREE.Vector3(10,10,2)//index:3
);
geometry.faces.push(
new THREE.Face3(0,1,2),//those numbers are indices of vertices in the previous array
new THREE.Face3(0,3,2)
);
geometry.computeFaceNormals();// we won't care about this here
(I did not care about the values so i do not know which shape it can give)
What you can see is that two arrays are built : vertices and faces. Now what happens at each frame is that each face is 'drawed' with the position of its vertices.
You ask what is wrong by deleting a vertex in the geometry.vertices array : let's imagine the second vertex above is deleted. The array now looks like this :
geometry.vertices=[
THREE.Vector3(12,15,5),//index:0
THREE.Vector3(12,10,2),//new index:1
THREE.Vector3(10,10,2)//new index:2
];
There is no more vertex at index 3. So when the GPU will draw the next frame, if a face points to it (here the second face) it will try to access its coordinates (first x before y and z). That is why the console returns that it cannot read x of undefined.
Here was a long explanation of the error. You can see the vertex deletion also shifted the array so faces do not have the correct shape, and their normals do not correspond anymore. The worst is that the buffer will have to change and that is simply not allowed, as stated there for example :
Dynamically Adding Vertices to a Line in Three.js
Adding geometry to a three.js mesh after render
The solution is to use tricks, as quoted : modify your vertex coordinates, hide faces... this depends on what you want to do.
If your scene has not much vertices you can also remove the previous mesh and create a new one with a new geometry, without one vertex and with a corrected face array.

Related

Mesh is not updating when changed from gui in threejs

I made a line mesh with elliptical shape, representing the orbit wiht eccentricity e, and semi-major axis a. The mesh is a child of a group called orbitGroup that contains other objects.
Also, I added a gui to change this parameters. Every time gui changes it calls the next function:
function ElementsUpdate(){
scene.remove(orbitGroup);
orbitGroup.remove(Orbit);
Orbit = undefined;
Orbit = new THREE.Line( GetGeometryOrbit(GetOrbitLine(a,e,100)), materialOrbit);
orbitGroup.add(Orbit);
scene.add(orbitGroup);
}
The mesh (Orbit) is being created successfully. However the it does not update. I'm aware that setGeometry method is not working anymore. Any solution? I am replacing the mesh because replacing only the geometry seems to be more complicated.
Thanks beforehand for the help.
The project is in this link
You should be able to replace the vertex (position) buffer and call it a day.
function ElementsUpdate(){
let points = GetOrbitLine(a,e,100).getPoints(); // THREE.Curve.getPoints
Orbit.geometry.setFromPoints( points ); // replaces the position buffer
}
Curve.getPoints gives you an array of the points on your elipse.
BufferBgeometry.setFromPoints replaces the position buffer, derived from your array of points.
Because it replaces the buffer (and the BufferAttribute) You should not need to mark anything as needing re-sent to the GPU.

ThreeJS - Project texture onto mesh surface

I'm looking to project a texture onto the surface of a mesh in ThreeJS.
https://www.lanyardmarket.com/en/printed-tshirt
This link achieves the result i'm looking for however i'm not sure how they achieved it.
.
I'll update this post as I research however if anyone knows how to project a ThreeJS texture onto a mesh i'd love to know.
Thanks
Working example you may find here: https://jsfiddle.net/mmalex/pcjbysn1/
BufferGeometry stores texture coordinates in 'uv' attribute, you can add it with BufferGeometry.addAttribute and access it through geom.attributes.uv.array.
let uvcoords = [];
let vertexCount = geom.attributes.position.array.length / 3;
// allocate array of UV coordinates (2 floats per each vertex)
uvcoords.length = 2 * vertCount;
if (geom.attributes.uv === undefined) {
geom.addAttribute('uv', new THREE.Float32BufferAttribute(uvcoords, 2));
}
Now all you need is to "project" mesh vertices onto some 3D plane. These projection coordinates will appear your UV coordinates.
In general case, you would need to do Plane.projectPoint for each vertex. This approach is straightforward and can be optimized with pre-rotating the mesh so that vertex x and y components become u and v accordingly. This you will find in my jsfiddle.

material map error : glDrawElements: attempt to access out of range vertices in attribute 2

I have an algorithm that lets users enter data or a function and then graphs the function. Essentially, its a surface map just like here. The graphing works fine. However, I want to add a mapping to add in black lines going from point to point just like in the example. However, I keep getting the standard error of : GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 2. I know that this error means that I am trying to draw something that is out of the range of the buffer, but I can't see where I am doing that. I have confirmed that the variables used for the x and y lengths given to the mapping are indeed the number of x and y indices. For example, I am setting the repeat to 76 and 76 for a mesh that has 76 x entries and 76 y entries and therefore 5776 points.
In the example, they use a parametric geometry and i have to use a standard THREE.Geometry since I am sometimes taking in data and not a function. Other than that, I have followed the example and still get the error. I may be confused about the mapping numbers required for the repeat on the texture. I believe that they are supposed to represent the number of X and Y points and maybe it is supposed to be something else.
Here is the method where I set my meshes:
Surface.prototype.setMeshes =function(DataID, callback)
{ var graphMesh={};
var PlotID=this.Format_id;
if (PlotID['Chart_dataobj'][DataID]['type']=="Surface")
{ if (PlotID['Chart_dataobj'][DataID]['sWireFrame']=="Solid")
{
var wireTexture = new THREE.ImageUtils.loadTexture( 'http://www.cadwolf.com/Images/square.png' );
wireTexture.wrapS = wireTexture.wrapT = THREE.RepeatWrapping;
wireTexture.repeat.set( parseInt(PlotID['Chart_dataobj'][DataID].xLength), parseInt(PlotID['Chart_dataobj'][DataID].yLength) );
wireMaterial = new THREE.MeshBasicMaterial( { map: wireTexture, vertexColors: THREE.VertexColors, side:THREE.DoubleSide } );
wireMaterial.map.repeat.set( parseInt(PlotID['Chart_dataobj'][DataID].xLength), parseInt(PlotID['Chart_dataobj'][DataID].yLength) );
graphMesh = new THREE.Mesh( window[PlotID]['Chart_dataobj'][DataID].surfaceGeometry, wireMaterial );
graphMesh.doubleSided = true;
}
}
graphMesh.id = DataID;
graphMesh.name = DataID;
window[PlotID].Scene.add(graphMesh);
if (typeof(callback)=="function") { callback(); }
}
I know that everything is correct with the surface geometry, there is just an error with the number of elements I have matching the numbers for the repeat. Also, if anyone knows what attribute 2 is, that would be helpful.
Thanks
Check your geometry to see if you have UVs defined. If not, then the lack of UVs is likely the cause of your error message.
A mesh cannot have a texture applied if the mesh's geometry is missing UVs.
For THREE.Geometry, UVs are specified in the property faceVertexUVs.
For THREE.BufferGeometry, UVs are specified as a geometry attribute.
three.js r.71

geometry.mergeVertices() only at render time

Is it possible to merge vertices only at render time? I'm doing a series of morphs which requires the vertex list to stay the same, however I want to merge the vertices to get a smooth reflection on a cube camera. Any one aware of a command similar to unmerge vertices?
Have you tried doing it? It should work.
You'll need to call
geometry.verticesNeedUpdate()
geometry.elementsNeedUpdate()
to tell three.js that the vertices and faces, respectively, have changed. There are other update functions you may need to call too (for instance if normals have changed). More details here: https://github.com/mrdoob/three.js/wiki/Updates
Note the comment on that page that the total number of vertices can't change. This may require you to do the merge on a temp geometry and then copy the vertices to your rendered geometry.
Alright, this is not in the documentation section, but you need to use the explode modifier as demonstrated in this example: http://threejs.org/examples/#webgl_geometry_tessellation
var explodeModifier = new THREE.ExplodeModifier();
explodeModifier.modify( geometry );
geometry.computeFaceNormals();
geometry.computeVertexNormals();
//This will undo the geometry.mergeVertices();

How do I 'wrap' a plane over a sphere with three.js?

I am relatively new to three.js and am trying to position and manipulate a plane object to have the effect of laying over the surface of a sphere object (or any for that matter), so that the plane takes the form of the object surface. The intention is to be able to move the plane on the surface later on.
I position the plane in front of the sphere and index through the plane's vertices casting a ray towards the sphere to detect the intersection with the sphere. I then try to change the z position of said vertices, but it does not achieve the desired result. Can anyone give me some guidance on how to get this working, or indeed suggest another method?
This is how I attempt to change the vertices (with an offset of 1 to be visible 'on' the sphere surface);
planeMesh.geometry.vertices[vertexIndex].z = collisionResults[0].distance - 1;
Making sure to set the following before rendering;
planeMesh.geometry.verticesNeedUpdate = true;
planeMesh.geometry.normalsNeedUpdate = true;
I have a fiddle that shows where I am, here I cast my rays in z and I do not get intersections (collisions) with the sphere, and cannot change the plane in the manner I wish.
http://jsfiddle.net/stokewoggle/vuezL/
You can rotate the camera around the scene with the left and right arrows (in chrome anyway) to see the shape of the plane. I have made the sphere see through as I find it useful to see the plane better.
EDIT: Updated fiddle and corrected description mistake.
Sorry for the delay, but it took me a couple of days to figure this one out. The reason why the collisions were not working was because (like we had suspected) the planeMesh vertices are in local space, which is essentially the same as starting in the center of the sphere and not what you're expecting. At first, I thought a quick-fix would be to apply the worldMatrix like stemkoski did on his github three.js collision example I linked to, but that didn't end up working either because the plane itself is defined in x and y coordinates, up and down, left and right - but no z information (depth) is made locally when you create a flat 2D planeMesh.
What ended up working is manually setting the z component of each vertex of the plane. You had originaly wanted the plane to be at z = 201, so I just moved that code inside the loop that goes through each vertex and I manually set each vertex to z = 201; Now, all the ray start-positions were correct (globally) and having a ray direction of (0,0,-1) resulted in correct collisions.
var localVertex = planeMesh.geometry.vertices[vertexIndex].clone();
localVertex.z = 201;
One more thing was in order to make the plane-wrap absolutely perfect in shape, instead of using (0,0,-1) as each ray direction, I manually calculated each ray direction by subtracting each vertex from the sphere's center position location and normalizing the resulting vector. Now, the collisionResult intersection point will be even better.
var directionVector = new THREE.Vector3();
directionVector.subVectors(sphereMesh.position, localVertex);
directionVector.normalize();
var ray = new THREE.Raycaster(localVertex, directionVector);
Here is a working example:
http://jsfiddle.net/FLyaY/1/
As you can see, the planeMesh fits snugly on the sphere, kind of like a patch or a band-aid. :)
Hope this helps. Thanks for posting the question on three.js's github page - I wouldn't have seen it here. At first I thought it was a bug in THREE.Raycaster but in the end it was just user (mine) error. I learned a lot about collision code from working on this problem and I will be using it later down the line in my own 3D game projects. You can check out one of my games at: https://github.com/erichlof/SpacePong3D
Best of luck to you!
-Erich
Your ray start position is not good. Probably due to vertex coordinates being local to the plane. You start the raycast from inside the sphere so it never hits anything.
I changed the ray start position like this as a test and get 726 collisions:
var rayStart = new THREE.Vector3(0, 0, 500);
var ray = new THREE.Raycaster(rayStart, new THREE.Vector3(0, 0, -1));
Forked jsfiddle: http://jsfiddle.net/H5YSL/
I think you need to transform the vertex coordinates to world coordinates to get the position correctly. That should be easy to figure out from docs and examples.

Categories