Removing duplicate vertices from Three.js geometry - javascript

I'm having trouble with removing duplicate vertices from a SphereGeomerty. I want to get rid of the seam on side of the geometry, because it don't align well if I update the vertex positions.
Problem is that I can't create a new geometry with a filtered vertex position list, I get an
[.Offscreen-For-WebGL-000001B883B499D0]GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 0
error.
More strange, if I put back the original vertex list to the bufferGeometry nothing is rendered, but the error is gone:
let positions = sphere.attributes.position.array;
filteredGeometry.addAttribute( 'position', new BufferAttribute( positions, 3 ) );
I'm filtering vertices like this:
function removeDuplicateVertices(vertices) {
var positionLookup = [];
var final = [];
for( let i = 0; i < vertices.length-3; i += 3 ) {
var index = vertices[i] + vertices[i + 1] + vertices[i + 2];
if( positionLookup.indexOf( index ) == -1 ) {
positionLookup.push( index );
final.push(vertices[i])
final.push(vertices[i+1])
final.push(vertices[i+2])
}
}
return final;
}

The SphereBufferGeometry uses an index-attribute. So the vertices-array doesn't store triangles directly but just the points. The triangles are constructed from the additional index-attribute which contains three indices into the position-attribute for each triangle. If you modify the position-attribute you have to update the index-attribute accordingly.
Or you can use geometry.toNonIndexed() to convert the "position + index" format to the "just position" format.
Also have a look at the Geometry.mergeVertices() function, which does exactly what you are doing, only for regular (not Buffer-) Geometries.
This is also called when building a regular SphereGeometry, maybe that already helps?

Related

How can I get the particular point in a THREE.Points object to change his position?

I work on a personal project to try out equations to try to simulate the behavior of a galaxy. I have so far managed to place the Points as I wanted, but now I want to take each point individually to change its position.
The goal for now is just to successfully try to apply a Random Vector to each of the points.
I tried:
var direction = new THREE.Vector3(0.00003, 0.000005, 0);
points.position.add(direction);
but this applies to all Points.
Then I tried something like that:
for (let i = 0; i < points.geometry.attributes.position.count; i++) {
points.geometry.attributes.position[i] = Math.random() * 500
}
points.geometry.attributes.position.needsUpdate = true;
But nothing append :( I thing I missed something but I dind't know what
Here the full code on codepen:
Codepen
When you access:
points.geometry.attributes.position[i]
you're not getting the array of the vertex positions. You're getting the BufferAttribute. What you probably want is the array inside the BufferAttribute:
points.geometry.attributes.position.array[i]
However, this is still not the recommended approach. Three.js recommends you use the .getAttribute() method:
// Get the attribute
const posAttribute = points.geometry.getAttribute("position");
// Get the array inside the attribute
const posArray = posAttribute.array;
// Increment by 3 at a time to access XYZ separately
for(let i3 = 0; i3 < posArray.length; i3 += 3) {
posArray[i3 + 0] = xPosition;
posArray[i3 + 1] = yPosition;
posArray[i3 + 2] = zPosition;
}
// Tell the attribute it needs updatin
posAttribute.needsUpdate = true;

STL loading and incorrect world matrix access

for a three.js project I have I have run into a few problems loading vertices from an STL and adequately converting them to world coordinates. It seems the matrix isn't being applied properly and, I think, it might be related to the loading mechanism itself.
loader.load( './assets/models/trajan_print.stl', function ( geometry ) {
var mesh = new THREE.Mesh( geometry, material );
mesh.name = "target";
mesh.position.set( 0, - 300, - 400 );
mesh.rotation.set( - Math.PI / 2, 0, Math.PI );
mesh.scale.set( 5, 5, 5 );
//mesh.castShadow = true;
//mesh.receiveShadow = true;
mesh.visible = false;
SCENE.add( mesh );
model.setTargets(mesh);
} );
the important function to note is the last one. model.setTargets(mesh). I'm interested in the vertices of the object in world coordinates and that's what that function does... kinda of:
setTargets(mesh){
this.matrixWorld = mesh.matrixWorld; //THIS WORKS, PRINTING IT REVEALS VALUES TRANSLATION/SCALE/ROTATION THAT MATCH THE MODEL'S
var buffer = mesh.geometry.attributes.position.array;
for(var i = 0; i < buffer.length /3; i = i + 3){
var point = new THREE.Vector3(buffer[i], buffer[i+1],buffer[i+2]);
point.applyMatrix4(this.matrixWorld);//DOES NOT WORK
this.unassignedVertices.push(point);
}
}
Now if I do the exact same operation outside of this function it will work as intended. This one is only called if this.unassignedVertices so it was my way around the fact that I needed to wait for the asynchronous load to happen.
insertParticle(part) {
var point = this.unassignedVertices.pop();
point.applyMatrix4(this.matrixWorld); //THIS WORKS BUT HERE BUT WHY?
part.setTargetPoint(point);
this.octree.add(part);
this.particles.add(part);
}
Problem number two, relates back to setTargets(mesh) I seem to only be loading around only half of the vertices from mesh.geometry.attributes.position.array. Now this can actually be caused by other parts in the code and I think that is something that falls outside the scope of a SO question so my question is if anything on that function could be responsible for it? Am I loading it improperly, am I converting it wrong, am I skipping points?
As for further context : the model loads and displays just fine if I remove the visible = false tag.
Ok so if anyone runs into this problem. The array will have duplicate positions as not all of them refer to vertices (probably). Simple case of if(position.x == ... cleans it right up to what's expected.

Three JS Raycasting - Find point closest to cursor

Three.js r85
When raycasting with Three JS, a series of points is returned, and I'd like to find the point that is closest to the cursor. The first point returned seems to be the point that is closest to the camera.
Is there a way to find the distance between the cursor position and a point?
Here's the code I'm using to debug this right now:
var raygun = new THREE.Raycaster();
raygun.setFromCamera(mouse, camera);
var hits = raygun.intersectObjects([plotpoints]);
if (hits.length > 0) {
scope.remove(dotPlot);
scope.remove(dotPlot2);
// All points except the first one - Grey
dotGeo = new THREE.Geometry();
for (var i=1; i < hits.length; i++) {
dotGeo.vertices.push(plotpoints.geometry.vertices[hits[i].index]);
}
dotPlot = new THREE.Points(dotGeo, dotMat);
scope.add(dotPlot);
// First point - Orange
var geo2 = new THREE.Geometry();
geo2.vertices.push(plotpoints.geometry.vertices[hits[0].index]);
dotPlot2 = new THREE.Points(geo2, dotMat2);
scope.add(dotPlot2);
scope.render();
}
And here's what I'm seeing:
Ah, figured it out with math!
First thing to note is that hits[].points returns a point directly under the cursor, but it doesn't "snap" to points.
In order to get the actual position of the point, we need to use hits[].index first to get the index number of the point/vertex we hit. We can then access that vertex directly by using GEOMETRY.vertices[] which returns a THREE.Vector3 of the vertex point we hit with our raycast.
So by feeding in the index, we can get the exact position of each vertex hit by our raycast:
GEOMETRY.vertices[hits[i].index]
This provides rudimentary "snapping" to vertices.
Note: When using THREE.LineSegments, the result will always be the starting point, and not the ending point. To get the ending point, you can just add 1 to the index value:
GEOMETRY.vertices[hits[i+1].index]
To snap directly to the vertex closest to the cursor, we need to find the vertex that has the shortest perpendicular distance from the raycaster's ray. To do this we use a cross product of 2 vectors. This is more of a math concept than a programming concept though, so if you want to understand the why behind this, look up something like: perpendicular distance from a point to a line
I just took the code from this question and translated it: http://answers.unity3d.com/questions/568773/shortest-distance-from-a-point-to-a-vector.html
And the end result:
// Variables to record and compare
var smallestDist = 99;
var smallestPointIndex = 0;
// Declare variables outside of loop to save memory
var m_ray = raycaster.ray;
var raydir = m_ray.direction;
var origin = m_ray.origin;
var hitray = new THREE.Vector3(0,0,0);
var dist = 1;
// Loop over all points to find the closest
for (var i=0; i<hits.length; i++){
// Math is magic
hitray.subVectors(plotpoints.geometry.vertices[hits[i].index], origin);
dist = new THREE.Vector3().crossVectors(raydir, hitray).lengthSq();
// Record the closest point
if (dist < smallestDist) {
smallestDist = dist;
smallestPointIndex = i;
}
}
// Now we can use that single point
Here's the result :)

How to update the topology of a geometry efficiently in ThreeJS?

I want to avoid creating new typed arrays and the consequent gc().
I made my geometry using BufferedGeometry. Upon receiving events, my vertex and the faces indices are updated. I can update the coordinates by setting verticesNeedUpdate but it does not update the faces. The update is called ~20-50 times per second, which can be heavy on the browser. How can I do this by avoiding creating heavy garbage for the JavaScript Garbage Collector? (See method update() below).
function WGeometry77(verts, faces) {
THREE.Geometry.call( this );
this.type = 'WGeometry77';
this.parameters = {};
// Initially create the mesh the easy way, by copying from a BufferGeometry
this.fromBufferGeometry( new MyBufferGeometry77( verts, faces ) );
};
WGeometry77.prototype = Object.create( THREE.Geometry.prototype );
WGeometry77.prototype.constructor = WGeometry77;
WGeometry77.prototype.update = function(verts, faces) {
var geom = this;
var nl = Math.min(geom.vertices.length, verts.length/3);
for ( var vi = 0; vi < nl; vi ++ ) {
geom.vertices[ vi ].x = verts[vi*3+0];
geom.vertices[ vi ].y = verts[vi*3+1];
geom.vertices[ vi ].z = verts[vi*3+2];
}
var nf = Math.min(geom.faces.length, faces.length/3);
for ( var fi = 0; fi < nf; fi ++ ) {
geom.faces[ fi ].a = faces[fi*3+0];
geom.faces[ fi ].b = faces[fi*3+1];
geom.faces[ fi ].c = faces[fi*3+2];
}
geom.verticesNeedUpdate = true; // Does not update the geom.faces
}
PS. My code is written in Emscripten, which does something like this:
var verts = Module.HEAPF32.subarray(verts_address/_FLOAT_SIZE, verts_address/_FLOAT_SIZE + 3*nverts);
What I want to do is almost animating, or a dynamic geometry (calculated using Marching Cubes). But my topology (the graph of the mesh) is also updated. Which ThreeJS class I should use? If there exists no such class, should I create we create a new class like UpdatableBufferedGeometry?
To update THREE.BufferGeometry after it has rendered, you can use this pattern:
geometry.attributes.position.setXYZ( index, x, y, z );
geometry.attributes.position.needsUpdate = true;
For indexed BufferGeometry, you can change the index array like so:
geometry.index.array[ index ] = value;
geometry.index.needsUpdate = true;
You cannot resize buffers -- only change their contents. You can pre-allocate larger arrays and use
geometry.setDrawRange( 0, numVertices );
three.js r.78
If you want efficiency, you should create a BufferGeometry instead of a Geometry.
You can use the source code of this example as reference:
http://threejs.org/examples/#webgl_buffergeometry_uint

Custom THREE.Curve.create displays incorrectly

As suggested in this answer, I've created a linearly interpolated curve like this:
THREE.Linear3 = THREE.Curve.create(
function ( points, label /* array of Vector3 */) {
this.points = (points == undefined) ? [] : points;
this.label = label;
},
function ( t ) {
var v = new THREE.Vector3();
var c = [];
var points = this.points, point, intPoint, weight;
point = ( points.length - 1 ) * t;
intPoint = Math.floor( point );
weight = point - intPoint;
c[ 1 ] = intPoint;
c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1;
var pt1 = points[ c[1] ],
pt2 = points[ c[2] ];
v.copy( pt1 ).lerp( pt2, weight );
return v;
}
);
However, when I'm trying to display a trajectory at different lengths (in an animated kinda-way) I get the following behavior i.e. instead of the curve going through the points, it kinda cuts through the space, note that in the example below each trajectory is supposed to go through the coordinates of each of the spheres (animated gif below):
I am not sure I understand the getPoint function or what is it supposed to return. Any Help is greatly appreciated.
JSFiddle
This is a minimal example but you can see how the right corner has a jerky motion as the tube expands.
http://jsfiddle.net/ElDeveloper/3uyf3sq3/1/
Cleaning some code
That helped me investigate the issue.
You are leaking geometries, you need to dispose the geometry after removing the mesh from the scene
scene.remove(c_mesh)
c_tube && c_tube.dispose();
Use WebGLRenderer. The CanvasRenderer leaks removed objects, and you're creating new objects on each frame. (If you're stuck with CanvasRenderer for some reason, sorry for you)
(For the fiddle) slow the motion, requestAnimationFrame isn't required for a test, setTimeout(animate, 500); allows the user to see what's happening.
What's the point of a 0-segment tube ?
if (index >= points.length - 1){
index = 1; //start with 2 points
}
Works as expected
The TubeGeometry does a tube of N (second argument in constructor, 16 in fiddle) segments. (I'll come back to that later, but I don't think you always want 16 segments)
The default behaviour of Curve.getPoinAt (the method used by TubeGeometry and probably lots of other geometries) is to return equidistant points. You can expect: distance(getPointAt(0),getPointAt(0.1)) == distance(getPointAt(0.1),getPointAt(0.2)) to be true.
Because of these points, no matter how many points you put in your path, the TubeGeometry will build a 16-segments tube with all segment of the same length, going from the first to the last point of your path. There is little chance that one of the 15 intermediate points will be exactly at the position of an edge. That should explain what you see.
Trying to fix the stuff
First get rid of that equidistant way-to-be of the TubeGeometry+Path. Overloading getUtoTmapping should be enough (I found that reading the source):
THREE.Linear3.prototype.getUtoTmapping = function(u) {
return u;
};
I changed your getPoint. It probably does the same thing, but I was more comfortable with my code the investigate
function ( t ) {
var points = this.points;
var index = ( points.length - 1 ) * t;
var floorIndex = Math.floor(index);
if(floorIndex == points.length-1)
return points[floorIndex];
var floorPoint = points[floorIndex];
var ceilPoint = points[floorIndex+1];
return floorPoint.clone().lerp(ceilPoint, index - floorIndex);
}
Give the correct number of segments to the TubeGeometry constructor:
var pathPoints = points.slice(0, index);
c_path = new THREE.Linear3(pathPoints, 'Test');
c_tube = new THREE.TubeGeometry(c_path, pathPoints.length-1, 10, 16, false, true);
At this point, you should have roughly what you were expecting
You should see the tube always going through the edges. You should also see that the TubeGeometry isn't meant to have angles. You could improve this angle issue either by looking at how TubeGeometry and Curve handles tangents, or (if you don't care about slowness) by increasing the number of segments to a very large number:
c_tube = new THREE.TubeGeometry(c_path, 200/*pathPoints.length-1*/, 10, 16, false, true);
That's all for the answer. You can find the last version of my experiments here: http://jsfiddle.net/dqn73m98/5/. You can also ask the three.js developers if such a feature exists, or request an implementation in a github issue (if someone as time to do it), here

Categories