Three.js memory allocation & workflow question - memory-management

Let’s say I want to make 100 objects - for example cars, like the one you see here:
This car is currently comprised of 5 meshes: one yellow Cube and four blue Spheres
What I’d like to know is what would be the most efficient/correct way to make 100 of these cars - or maybe 500 - in terms of memory management/ CPU performance, etc.
The way I’m currently going about doing this is as follows:
Make an empty THREE.Group called “newCarGroup” -
Create the yellow rectangular Mesh for the body of the car - called “carBodyMesh”
Create four blue Sphere Meshes for the Tires called “tire1Mesh”, “tire2Mesh”, “tire3Mesh”, and “tire4Mesh”
Add the Body and the four Tires to the “newCarGroup”
And finally, in a FOR loop, create/instantiate 100 “newCarGroup” objects, adding each one to the SCENE at a random position
The code is below.
It's working perfectly well right now, but I’d like to know if this is the “proper”/best way to do this?
Consider it’s possible I might end up needing 1,000 cars - or 5,000 cars. So will this scale properly?
Also, I need to add more objects to the car: like 4 windows - actually make that 6 windows, to also include the front and back windshields, then four headlights, etc.
So the final Car Object alone may end up being comprised of 20 meshes - or more.
Being that I’m kinda new to THREE.JS I wanna make sure I develop good habits and go about this sort of thing the right way.
Here’s my code:
function makeOneCar() {
var newCarGroup = new THREE.Group();
// 1. CAR-Body:
const bodyGeometry = new THREE.BoxGeometry(30, 10, 10);
const bodyMaterial = new THREE.MeshPhongMaterial({ color: "yellow" } );
const carBodyMesh = new THREE.Mesh(bodyGeometry, bodyMaterial);
// 2. TIRES:
const tireGeometry = new THREE.SphereGeometry(2, 16, 16);;
const tireMaterial = new THREE.MeshPhongMaterial( { color: "blue" } );
const tire1Mesh = new THREE.Mesh(tireGeometry, tireMaterial);
const tire2Mesh = new THREE.Mesh(tireGeometry, tireMaterial);
const tire3Mesh = new THREE.Mesh(tireGeometry, tireMaterial);
const tire4Mesh = new THREE.Mesh(tireGeometry, tireMaterial);
// TIRE 1 Position:
tire1Mesh.position.x = carBodyMesh.position.x - 11;
tire1Mesh.position.y = carBodyMesh.position.y - 4.15;
tire1Mesh.position.z = carBodyMesh.position.z + 4.5;
// TIRE 2 Position:
tire2Mesh.position.x = carBodyMesh.position.x + 11;
tire2Mesh.position.y = carBodyMesh.position.y - 4.15;
tire2Mesh.position.z = carBodyMesh.position.z + 4.5;
// TIRE 3 Position:
tire3Mesh.position.x = carBodyMesh.position.x - 11;
tire3Mesh.position.y = carBodyMesh.position.y - 4.15;
tire3Mesh.position.z = carBodyMesh.position.z - 4.5;
// TIRE 4 Position:
tire4Mesh.position.x = carBodyMesh.position.x + 11;
tire4Mesh.position.y = carBodyMesh.position.y - 4.15;
tire4Mesh.position.z = carBodyMesh.position.z - 4.5;
// Putting it all together:
newCarGroup.add(carBodyMesh);
newCarGroup.add(tire1Mesh);
newCarGroup.add(tire2Mesh);
newCarGroup.add(tire3Mesh);
newCarGroup.add(tire4Mesh);
// Setting (x, y, z) Coordinates - RANDOMLY
let randy = Math.floor(Math.random() * 10);
let newCarGroupX = randy % 2 == 0 ? Math.random() * 250 : Math.random() * -250;
let newCarGroupY = 0.0;
let newCarGroupZ = randy % 2 == 0 ? Math.random() * 250 : Math.random() * -250;
newCarGroup.position.set(newCarGroupX, newCarGroupY, newCarGroupZ)
scene.add(newCarGroup);
}
function makeCars() {
for(var carCount = 0; carCount < 100; carCount ++) {
makeOneCar();
}
}

I’d like to know if this is the “proper”/best way to do this?
This is subjective. You say the method works great for your current use-case, so for that use-case, it is fine.
So will this scale properly?
The simple answer is: No. The more complex answer is: ...not really.
You're re-using the geometry and materials, which is good. But every Mesh you create has meta information surrounding it, which adds to your overall memory footprint.
Also, every standard Mesh you add incurs what is known as a "draw call", which is the GPU drawing that particular shape. Instead, take a look at InstancedMesh. This allows the GPU to be given instructions on how to draw the shape throughout the scene once. Yes, rather than drawing each cube individually, the GPU can draw all the cubes at the same time, and they can even have different colors and transformations. There are limitations to this class, but it's a good starting point to understanding how instancing works.

Related

THREE.js Mutating vertices of Plane according to Data from mp3

So i've been stuck for a while because i've been having trouble dynamically changing the shape of the vertices in a place geometry according to the frequency data of an mp3, I've been having 2 main problems:
1)The array generated by the mp3 has too many values and it is impossible to render out the vertices that fast and accordingly, i am getting the frequency data with this code.
var frequencyData = new Uint8Array(analyser.frequencyBinCount);
2) Re-Rendering the plane everytime frequencyData changes causes extreme performance issues to the point it does not render out anymore
I've been using simplex noise to cause the vertices to morph, and it does work until obviously i pass in frequency data and everything breaks, this is the code i'm trying to use to morph the vertices of the plane according to the music.
function adjustVertices() {
for (var i = 0; i < 100; i++) {
for (var j = 0; j < 100; j++) {
var ex = 0.5;
pgeom.vertices[i + j * 100].z =
(noise.simplex2(i / 100, j / 100) +
noise.simplex2((i + 500) / 50, j / 50) * Math.pow(ex, frequencyData[2]) +
noise.simplex2((i + 400) / 25, j / 25) * Math.pow(ex, frequencyData[2]) +
noise.simplex2((i + 600) / 12.5, j / 12.5) * Math.pow(ex, frequencyData[2]) +
+(noise.simplex2((i + 800) / 6.25, j / 6.25) * Math.pow(ex, frequencyData[2]))) /
2;
pgeom.verticesNeedUpdate = true;
pgeom.computeVertexNormals();
}
}
}
This is my plane object:
var pgeom = new THREE.PlaneGeometry(5, 5, 99, 99);
var plane = THREE.SceneUtils.createMultiMaterialObject(pgeom, [
new THREE.MeshPhongMaterial({
color: 0x33ff33,
specular: 0x773300,
side: THREE.DoubleSide,
shading: THREE.FlatShading,
shininess: 3,
}),
]);
scene.add(plane);
I am very grateful for the help, I am just doing my best in mastering three.js :)
I would check if the computeVertexNormals is what is taking the most time in that render loop, and then look into optimizing it, if you still require it.
You can optimize the normal calculation by building the mesh topology once at startup, since it doesn't change at runtime, making the recalc run in constant time.
Then reduce the vertex count until things become manageable. :)
The first answer is correct. Most likely computing vertex normals is causing the hit, and it's most likely happening because the Geometry method which you seem to be using creates a lot of new THREE.Vector3. If you profile this i imagine you'd see a lot of GC activity and not so much of computation time.
One more thing to consider since you only map one variable, is to move this computation in the shader. You could write your values to a texture and only update that. You would not have to refresh the vertex and normal buffers which are much larger than the texture you'd need to store just the input variable. You would also be able to do this computation in parallel.

Three.js merging mesh/geometry objects

I'm creating a three.js app which consists of floor (which is composed of different tiles) and shelving units (more than 5000...). I'm having some performance issues and low FPS (lower then 20), and I think it is because I'm creating a separate mesh for every tile and shelving unit. I know that I can leverage geometry/mesh merging in order to improve performance. This is the code for rendering the floor and shelving units (cells):
// add ground tiles
const tileGeometry = new THREE.PlaneBufferGeometry(
1,
1,
1
);
const edgeGeometry = new THREE.EdgesGeometry(tileGeometry);
const edges = new THREE.LineSegments(edgeGeometry, edgeMaterial);
let initialMesh = new THREE.Mesh(tileGeometry, floorMat);
Object.keys(groundTiles).forEach((key, index) => {
let tile = groundTiles[key];
let tileMesh = initialMesh.clone();
tileMesh.position.set(
tile.leftPoint[0] + tile.size[0] / 2,
tile.leftPoint[1] + tile.size[1] / 2,
0
);
tileMesh.scale.x = tile.size[0];
tileMesh.scale.y = tile.size[1];
tileMesh.name = `${tile.leftPoint[0]}-${tile.leftPoint[1]}`;
// Add tile edges (adds tile border lines)
tileMesh.add(edges.clone());
scene.add(tileMesh);
});
// add shelving units
const cellGeometry = new THREE.BoxBufferGeometry( 790, 790, 250 );
const wireframe = new THREE.WireframeGeometry( cellGeometry );
const cellLine = new THREE.LineSegments(wireframe, shelves_material);
Object.keys(cells).forEach((key, index) => {
let cell = cells[key];
const cellMesh = cellLine.clone();
cellMesh.position.set(
cell["x"] + 790 / 2,
// cell["x"],
cell["y"] + 490 / 2,
cell["z"] - 250
);
scene.add(cellMesh);
});
Also, here is a link to a screenshot from the final result.
I saw this article regarding merging of geometries, but I don't know how to implement it in my case because of the edges, line segments and wireframe objects I'm using..
Any help would be appriciated
Taking into account #Mugen87's comment, here's a possible approach :
Pretty straightforward merging of planes
Using a shader material to draw "borders"
Note : comment out the discard; line to fill the cards with red or whatever material you might want.
JsFiddle demo

Three.js - Arranging cubes in a grid

I would like to position cubes in a rectangular/square like grid. I'm having trouble trying to create some methodology in depending on what I pick through an HTML form input (checkboxes) to have it arrange left to right and up to down, a series of cubes, in a prearranged grid all on the same plane.
What measurement units is three.js in? Right now, I'm setting up my shapes using the built-in geometries, for instance.
var planeGeometry = new THREE.PlaneGeometry(4, 1, 1, 1);
The 4 and 1; I'm unsure what that measures up to in pixels, although I do see it rendered. I'm resorting to eyeballing it (guess and checking) every time so that it looks acceptable.
Without a fair bit of extra math THREE is not measured in pixels.
To make a simple grid (I leave optimizations, colors, etc for future refinements) try something like:
var hCount = from_my_web_form('horiz'),
vCount = from_my_web_form('vert'),
size = 1,
spacing = 1.3;
var grid = new THREE.Object3d(); // just to hold them all together
for (var h=0; h<hCount; h+=1) {
for (var v=0; v<vCount; v+=1) {
var box = new THREE.Mesh(new THREE.BoxGeometry(size,size,size),
new THREE.MeshBasicMaterial());
box.position.x = (h-hCount/2) * spacing;
box.position.y = (v-vCount/2) * spacing;
grid.add(box);
}
}
scene.add(grid);

threejs selecting different parts of a mesh

I'm using THREE.js. I have a model of a human that I want to be able to select different portions of. For example, if you click on one of the legs a particular action will be executed. My original idea was to split the model up into separate meshes and then use raytracing to determine which object was selected. But now when i render the scene, the shading along the edges of each mesh doesn't blend with adjoining meshes. This leaves ragged looking lines across the model between selectable portions. Is there a way to blend the shading between the mesh pieces I've created? Or is there a better way to select part of a mesh other than creating separate meshes? I have some programming experience, but this is the first time I've tried to use three.js. Any insight would be greatly appreciated.
You may create additional attribute for each triangle, that would be color of the bodypart that it belongs to. So, all triangles of the left leg would be red, all triangles of right leg would be blue etc.
Render your model normally, and add second pass where you would render triangles colored in the way described above, so no shading at all. Then, you could get your mouse position where the user clicked and look up in that bodypart-colored framebuffer and just check the pixel color on the place where user clicked.
This technique of picking 3d objects by assigning them different colors, rendering those colors to another texture and then checking color of clicked pixel is quite common, although it has some flaws. On the other hand, neither is ray testing absolutely accurate.
I believe that this demo runs actually based on that concept - demo.
var aiGeojj = new t.CubeGeometry(30, 30, 30);
var uprighters = Math.floor((Math.random() * 11));
var aiMaterialjj = new t.MeshBasicMaterial({ map: t.ImageUtils.loadTexture('images/images_bots/greenbot/upright/' + uprighters + '.gif'), opacity: 0, transparent: true });
var ojj= new t.Mesh(aiGeojj, aiMaterialjj);
ojj.limbs = [];
ojj.trunk = [];
var aiGeojjkey2c = new t.CubeGeometry(50, 50, 50);
var uprightersc = Math.floor((Math.random() * 11));
var aiMaterialjjc = new t.MeshBasicMaterial({ map: t.ImageUtils.loadTexture('images/images_bots/greenbot/upright/' + uprightersc + '.gif'), opacity: 1, transparent: true });
var ojjkey2c = new t.Mesh(aiGeojjkey2c, aiMaterialjjc);
ojjkey2c.id = "hiworld";
ojj.add(ojjkey2c);
ojj.trunk.push(ojjkey2c);
for( var you = 0; you < ojj.length; you++){
for( var youb = 0; youb < ojj[you].trunk.length; youb++){
window.alert( ojj[you].trunk[youb].id);
}
}

Setting color of mapped image for ThreeJS particles

Originally I was using ParticleSystem, but I discovered that Raycaster does not work with it. So I'm now modifying my code to simply use individual Particle objects.
The problem is, I can't seem to set the color of the image I'm mapping to the particles like I was able to with ParticleSystem.
I tried the following:
texture = THREE.ImageUtils.loadTexture("ball.png");
material = new THREE.ParticleBasicMaterial({
size : 10,
color: 0x00C1BF,
map : texture,
transparent : true,
});
// Generate some random points...
for (var i = 0; i < pointCount; i++) {
var particle = new THREE.Particle(material);
particle.position.x = Math.random() * (max - min) + min;
particle.position.y = Math.random() * (max - min) + min;
particle.position.z = Math.random() * (max - min) + min;
particle.scale.x = particle.scale.y = particle.scale.z = 3;
plot.add(particle);
}
But the color of ball.png remains the same. If I comment out the image I'm mapping to the points, the colors are changing. But it's not working with the mapped image. When I was using ParticleSystem, inside the for loop where I generate the points, I was adding this:
colors[i] = new THREE.Color(0xffffff);
colors[i].setHSL((x + 1000 ) / 2000, 1, 0.5);
And then set particleSys.colors = colors; outside the loop. That changed the color of the points, but this doesn't seem to work with Particle.
I hate to keep bugging the community with questions like this, but I really would appreciate any guidance on this. Many thanks, as always! :)
Also, here's a link to ball.png that I'm using: http://threejsdoc.appspot.com/doc/three.js/examples/textures/sprites/ball.png

Resources