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

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

Related

Removing duplicate vertices from Three.js geometry

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?

three.js selecting children of Object3D using raycaster.intersectObject

I am trying to make a series of cubes that can be clicked to highlight them. This will enable me to change their color or add a texture or manipulate them in some way. I have looked through the source code of all the interactive examples at https://threejs.org/examples/ and it appears that each example uses a slightly different way of creating and selecting objects in the scene. I am not used to using javascript though, so maybe I'm missing something simple.
I create an Object3D class named blocks to store all of the cubes
blocks = new THREE.Object3D()
I am using a for loop to create a 9 x 9 array of cubes starting at (0,0,0) coordinates with a slight gap between them, and add() them to blocks and add() blocks to the scene. example: (cube size 2,2,2)
function stack(mx,my,mz){
for (var i = 0; i < 9; i++){
line(mx,my,mz);
mz += 3;
}
}
function line(mx,my,mz){
for (var i = 0;i<9;i++){
var block = new THREE.Mesh( Geometry, Material);
block.position.x = mx;
block.position.y = my;
block.position.z = mz;
blocks.add(block);
mx+=3;
}
}
stack(mx,my,mz)
scene.add(blocks)
When I run this code, I can see them rendered. I use raycaster to .intersectObjects() which requires an array of objects. This is where I run into the problem of selecting just one object.
function onDocumentMouseDown(event) {
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
**var intersects = raycaster.intersectObjects(blocks.children, true);**
if (intersects.length > 0) {
intersects[0].object.material.transparent = true;
other code stuff blah blah blah
{
This will make all children clickable but they have the same .id as the first object created. so if I try to .getObjectById() in order to change something, it doesn't work.
I have tried to generate each element and add them to the scene iteratively instead of creating an object array to hold them and it still has a similar effect. I've tried storing them in a regular array and then using true parameter to recursively search the .intersectObject() array but it selects all of the objects when I click on it.
var intersects = raycaster.intersectObjects(blocks, true);
I have considered creating 81 unique variables to hold each element and statically typing an array of 81 variables (desperate option) but I can't find a secure way to dynamically create variable names in the for loop to hold the objects. This way was posted on stackoverflow as a solution to creating different named variables but it doesn't seem to create variables at all.
for (var i=0, i<9, i++){
var window["cube" + i] = new THREE.Mesh( Geometry, Material)
{
Main Question: How can I iteratively create multiple Mesh's (enough that statically typing each variable would be ill-advised) in a controllable way that I can select them and manipulate them individually and not as a group?
I think the reason why you met this problem is you reference same Material to build your Mesh, you did intersect a single object in blocks.children, but when you change some properties of the material others mesh who use the material would change too.
function line(mx,my,mz){
for (var i = 0;i<9;i++){
material = new THREE.MeshLambertMaterial({color: 0xffffff});
var block = new THREE.Mesh( Geometry, material);
block.position.x = mx;
block.position.y = my;
block.position.z = mz;
blocks.add(block);
mx+=3;
}
}
it works for me.

ThreeJS - multiple meshes from a .json 3D file

I exported a .json from the online 3D editor and I'm trying to load it and instantiate 20 versions of it, like this example. My code is flawed bc all 20 versions are actually acting like the same object. Not sure why they're not being added to the scene as separate objects in their given x,z coordinates.
var serverObject;
var allBrains = []
var xCenter;
var zCenter;
var spacing = .2;
var newServ;
var objectLoader = new THREE.ObjectLoader();
objectLoader.load("asset_src/model.json", function(obj) {
//give it a global name, so I can access it later?
serverObject = obj
//see what's inside of it
obj.traverse(function(child) {
if (child instanceof THREE.Mesh) {
console.log(child)
}
})
//was trying to add transparency this way, but ended up
//going through the online editor to apply it
// var cubeMaterial1 = new THREE.MeshBasicMaterial({
// color: 0xb7b7b7,
// refractionRatio: 0.98
// });
//Do i need to instantiate my mesh like this, if so, how do I make sure that it gets the materials from the json? The json has 6 children each with a different material
// serverObject = new THREE.Mesh( obj, cubeMaterial1 );
//make 20 versions of the file
for (var i = 0; i < 20; i++) {
xCenter = Math.cos(toRadians(i * spacing))
zCenter = Math.sin(toRadians(i * spacing))
serverObject.scale.set(.09, .09, .09)
//this amount of offset is correct for the scale of my world
//i was going to use my xCenter, zCenter but tried to simplify it till it works
serverObject.position.set((i * .1), controls.userHeight - 1, i * .1);
allBrains.push(serverObject)
//I've attempted a number of ways of adding the obj to the scene, this was just one
scene.add(allBrains[i]);
}
// see that there are 20 meshes
console.log(allBrains)
});
The return of my last console log looks like this:
At the moment, you have a single object (serverObject) which you manipulate and add multiple times, but each iteration of the loop just modifies the same object, overriding previous parameters.
You need to clone your mesh, using the... clone() method. You'll then be able to modify the settings of that object (the copy), and each of the meshes will remain independent.
Alternatively, you could run the objectLoader.load method inside the loop to create the object multiple times from the JSON file, but it's probably a waste of resources.
Thanks to #jcaor for the clone() idea, here is the working code:
var objectLoader = new THREE.ObjectLoader();
objectLoader.load("asset_src/model.json", function(obj) {
//give it a global name, so I can access it later
serverObject = obj
//see what's inside of it
obj.traverse(function(child) {
if (child instanceof THREE.Mesh) {
console.log(child)
}
})
for (var i = 0; i < 20; i++) {
var tempNew = serverObject.clone()
xCenter = Math.cos(toRadians(i * spacing))
zCenter = Math.sin(toRadians(i * spacing))
tempNew.scale.set(.05, .05, .05)
tempNew.position.set(xCenter, controls.userHeight - 1, zCenter);
allBrains.push(tempNew)
scene.add(allBrains[i]);
}
console.log(allBrains)
});
This looks like a pointer issue to me.
In JavaScript you can think of variables like pointers that is why you don't have to assign types to variables and how functions can be variables and still work in normal computer science.
In this case you are assigning the same pointer to each slot in the array.
This is a super simple version of the problem. obj2.foo was never changed but because we changed obj.foo obj2.foo changed because of the var simply pointing to the same object.
var obj = {
foo : 1
}
var obj2 = obj;
obj.foo = 2;
console.log(obj2.foo);
What I would do is create a new object and popluate it with the information of the master object in you case "serverObject"
allBrains.push(serverObject)

Three.js Event Listener in scene renderer

This will be a "strange" question, because my code works. I just want to check if it can be tweaked because I dont have programming experience.
I wrote a script that gets a stream of kinect joints and plots them on a three js scene on an coordinates system with a webgl renderer.
At first I create a set of spheres, based on Stemkoski's examples and then add the event listener for the json skeleton that arrives in the update function, so I can update the spheres coordinates and have it moving.
The only problem that this creates is that now I cannot rotate the coordinates system with my mouse (while I could when I was testing it with static spheres or with pulsing spheres, as in Stemkoski's example).
So the first question is how can I correct this problem.
The second, more complex question is this : Is this a correct practice? I may not be a developer but I have learned that when my gut says this is awkward or bad practice, it probably is.. Any help and suggestions, appreciated.
Oh, and I have implemented this for only one skeleton in the scene. I will try to make it for more skeletons but I ll wait for your input here in case this all could be simpler and better written.
Here's the relevant code, the initialization of the sphere/joints and the update function:
WholeSkeleton = new THREE.Object3D();
particleAttributes = { startSize: [], startPosition: [] };
var totalParticles = 20; //=num.skeleton.joints //add later variable number
var radiusRange = 50;
for (var i = 0; i < totalParticles; i++) { // INITIALIZE THE SKELETON JOINTS POSITION
var sphereMaterial = new THREE.MeshLambertMaterial({ color: 'rgb(255,0,0)' });
var jointSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
//initialize positions
jointSphere.position.set(0, 0, 0);
WholeSkeleton.add(jointSphere);
// add variable qualities to arrays, if they need to be accessed later
particleAttributes.startPosition.push(jointSphere.position.clone());
}
scene.add(WholeSkeleton);
}
function animate() {
requestAnimationFrame(animate);
render();
update();
}
function update() {
document.addEventListener("skeletonEvent", function (e) {
jsonObject = e.detail.SkeletonSourceData;
// for (var i = 0; i < jsonObject.Skeletons.length; i++) {
for (var j = 0; j < jsonObject.Skeletons[0].Joints.length; j++) { //only for skeleton 0.
var incoming=jsonObject.Skeletons[0].Joints[j];
var joint = WholeSkeleton.children[j];
joint.position.x = (particleAttributes.startPosition[j].x + incoming.Position.X)*30;
joint.position.y = (particleAttributes.startPosition[j].y + incoming.Position.Y)*30;
joint.position.z = (particleAttributes.startPosition[j].z + incoming.Position.Z)*10;
}
// }
});
}
--

Three.js save modified mesh vertices r67

I have created a plane and when I click on a vertex, the vertex will move and render as expected . My problem is saving the mesh to a text file.The vertices don't seem to update on the file as expected.
If I move a vertex before the second render the vertex position will reflect on the external text file.
My question is once I have moved around a bunch of vertices x,y,z positions how do I save the result that appears on my screen as it seems to only save the original mesh .
var guiControls = new function () {
var t = ['Test Save File'];
this.save_mesh = function () {
for (var i = 0, j = ground.geometry.vertices.length; i < j; i++) {
t.push('['+ ground.geometry.vertices[i].x+','+ ground.geometry.vertices[i].y+ ','+ objects[0].geometry.vertices[i].z+ '#:'+']');
}
function passMesh(){
var data = {
value: t
};
$.post("./php/savefile.php", data);
}
passMesh();
};
I have tried to use the updates available:
geo.dynamic = true;
geo.computeVertexNormals();
geo.computeFaceNormals();
geo.verticesNeedUpdate = true;
geo.normalsNeedUpdate = true;
Any help is appreciated.
In 3D graphics all changes are only matrix transformations of the original mesh, which do not actually change this mesh. So my guess is that you need to apply the transformations to you objects prior to the export. try: .applyMatrix

Categories