buffergeometry with more than 40k vertices - three.js

I was loading more than 40k vertices in a BufferGeometry.
It worked, but the geometry was not rendered entirely. After decomposing the geometry into separate chunks of 40k vertices, it worked. I was using version r86.
Is this something related to the hardware I am using or to three js?
Please find bellow an adaptation of the code I used to create the BufferGeometry (normalgeom is a Geometry passed as parameter)
var positions = new Float32Array(chunkLength * 3);
var indices = new Uint16Array(chunkLength);
var chunkLength = normalgeom.vertices.length;
for (var i = 0; i < chunkLength; i++)
{
var posInNormalGeom = i;
positions[i * 3] = normalgeom.vertices[posInNormalGeom].x;
positions[i * 3 + 1] = normalgeom.vertices[posInNormalGeom].y;
positions[i * 3 + 2] = normalgeom.vertices[posInNormalGeom].z;
indices[i] = i;
}
var buffGeom = new THREE.BufferGeometry();
buffGeom.addAttribute('position', new THREE.BufferAttribute(positions, 3));
buffGeom.setIndex(new THREE.BufferAttribute(new Uint16Array(indices), 1));
Afterwards, I was creating LineSegments from the buffer
var lineSegs = new THREE.LineSegments(buffGeom, material);
scene.add(lineSegs);
Update: after comment from #TheJim01, the code for splitting the geometry is the following
function makebuffered(normalgeom)
{
var retArrays = new Array();
var chunkLength = normalgeom.vertices.length;
console.log("nr vertices:" + chunkLength);
var remainingVertices = chunkLength;
var processedVertices = 0;
if(chunkLength > 40000)
{
chunkLength = 40000;
}
while(remainingVertices > 0)
{
if(remainingVertices <= chunkLength)
{
chunkLength = remainingVertices;
}
var positions = new Float32Array(chunkLength * 3);
var indices = new Uint32Array(chunkLength);
for (var i = 0; i < chunkLength; i++)
{
var posInNormalGeom = processedVertices + i;
positions[i * 3] = normalgeom.vertices[posInNormalGeom].x;
positions[i * 3 + 1] = normalgeom.vertices[posInNormalGeom].y;
positions[i * 3 + 2] = normalgeom.vertices[posInNormalGeom].z;
indices[i] = i;
}
var buffGeom = new THREE.BufferGeometry();
buffGeom.addAttribute('position', new THREE.BufferAttribute(positions, 3));
buffGeom.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1));
retArrays.push(buffGeom);
remainingVertices -= chunkLength;
processedVertices += chunkLength;
}
return retArrays;
}

You have 847666 vertices, which leads to 847666 indices, which is well beyond the bounds of a Uint16Array (max = 65535).
To alleviate this, use Uint32Array instead. All modern browsers (of any consequence) support 32-bit arrays for WebGL buffers.

Related

How to convert `geometry.faceVertexUvs` in `THREE.js` current version?

const geometry = new THREE.SphereGeometry(100, 64, 64, Math.PI / 2, Math.PI, 0, Math.PI);
const uvs = geometry.faceVertexUvs[0];
const axis = 'x';
for (let i = 0; i < uvs.length; i += 1) {
for (let j = 0; j < 3; j += 1) {
uvs[i][j][axis] *= 0.5;
}
}
geometry.faceVertexUvs is depreciated.
How to convert this reference to THREE.js current version?
The new method uses BufferGeometry instead of Geometry. This stores each vertex attribute (position, normal, uv) in arrays, so you can fetch the UVs with BufferGeometry.getAttribute("uv");.
Once you've retreived the attribute, you'll end up with a BufferAttribute, where you can access the .array property for each individual component:
const geometry = new THREE.SphereGeometry(100, 64, 64, Math.PI / 2, Math.PI, 0, Math.PI);
const uvAttribute = geometry.getAttribute("uv");
const uvArray = uvAttribute.array;
// Loop through all UVs
// UVs have 2 components, so we jump by 2 on each iteration
for (let i = 0; i < uvAttribute.length; i += 2) {
uvArray[i + 0] = uvX;
uvArray[i + 1] = uvY;
}
// Now we set the update flag to true so the GPU gets the new values
uvAttribute.needsUpdate = true;

Unity - Improve Mesh generation and rendering performance

since I just recently started looking into meshes, how they work, what they do and so on, I decided to use my own calculations to create a mesh of a circle. Unfortunately though, this is really, really slow!
So I am looking for tips on improvements, to make it slow only (because that's probably the best it will get...)
Here is the code I use to generate a circle:
public static void createCircle(MeshFilter meshFilter, float innerRadius, float outerRadius, Color color, float xPosition = 0, float yPosition = 0, float startDegree = 0, float endDegree = 360, int points = 100)
{
Mesh mesh = meshFilter.mesh;
mesh.Clear();
//These values will result in no (or very ugly in the case of points < 10) circle, so let's safe calculation and just return an empty mesh!
if (startDegree == endDegree || points < 10 || innerRadius >= outerRadius || innerRadius < 0 || outerRadius <= 0)
{
return;
}
//The points for the full circle shall be whatever is given but if its not the full circle we dont need all the points!
points = (int)(Mathf.Abs(endDegree - startDegree) / 360f * points);
//We always need an uneven number of points!
if (points % 2 != 0) { points++; }
Vector3[] vertices = new Vector3[points];
float degreeStepSize = (endDegree - startDegree) * 2 / (points - 3);
float halfRadStepSize = (degreeStepSize) * Mathf.Deg2Rad / 2f;
float startRad = Mathf.Deg2Rad * startDegree;
float endRad = Mathf.Deg2Rad * endDegree;
//Let's save the vector at the beginning and the one on the end to make a perfectly straight line
vertices[0] = new Vector3(Mathf.Sin(startRad) * outerRadius + xPosition, Mathf.Cos(startRad) * outerRadius + yPosition, 0);
vertices[vertices.Length - 1] = new Vector3(Mathf.Sin(endRad) * innerRadius + xPosition, Mathf.Cos(endRad) * innerRadius + yPosition, 0);
for (int i = 1; i < vertices.Length - 1; i++)
{
//Pure coinsidence that saved some calculatons. Half Step Size is the same as what would needed to be calculated here!
float rad = (i - 1) * halfRadStepSize + startRad;
if (i % 2 == 0)
{
vertices[i] = new Vector3(Mathf.Sin(rad) * outerRadius + xPosition, Mathf.Cos(rad) * outerRadius + yPosition, 0);
}
else
{
vertices[i] = new Vector3(Mathf.Sin(rad) * innerRadius + xPosition, Mathf.Cos(rad) * innerRadius + yPosition, 0);
}
}
mesh.vertices = vertices;
int[] tri = new int[(vertices.Length - 2) * 3];
for (int i = 0; i < (vertices.Length - 2); i++)
{
int index = i * 3;
if (i % 2 == 0)
{
tri[index + 0] = i + 0;
tri[index + 1] = i + 2;
tri[index + 2] = i + 1;
}
else
{
tri[index + 0] = i + 0;
tri[index + 1] = i + 1;
tri[index + 2] = i + 2;
}
}
mesh.triangles = tri;
Vector3[] normals = new Vector3[vertices.Length];
Color[] colors = new Color[vertices.Length];
for (int i = 0; i < vertices.Length; i++)
{
normals[i] = Vector3.forward;
colors[i] = color;
}
mesh.normals = normals;
mesh.colors = colors;
meshFilter.mesh = mesh;
}
I know I "could just use the LineRenderer shipped with Unity, it is faster then anything you'll ever write", but that's not the point here.
I am trying to understand meshes and see where I can tweak my code to improve it's performance.
Thanks for your help in advance!
You can almost double the speed by removing extra memory allocation. Since Vector3 is a value type, they are already allocated when you allocate the array. Vector3.forward also allocates a new Vector3 each time, and we can re-use it.
public static void createCircle(MeshFilter meshFilter, float innerRadius, float outerRadius, Color color, float xPosition = 0, float yPosition = 0, float startDegree = 0, float endDegree = 360, int points = 100)
{
Mesh mesh = meshFilter.mesh;
mesh.Clear();
//These values will result in no (or very ugly in the case of points < 10) circle, so let's safe calculation and just return an empty mesh!
if (startDegree == endDegree || points < 10 || innerRadius >= outerRadius || innerRadius < 0 || outerRadius <= 0)
{
return;
}
//The points for the full circle shall be whatever is given but if its not the full circle we dont need all the points!
points = (int)(Mathf.Abs(endDegree - startDegree) / 360f * points);
//We always need an uneven number of points!
if (points % 2 != 0) { points++; }
Vector3[] vertices = new Vector3[points];
float degreeStepSize = (endDegree - startDegree) * 2 / (points - 3);
float halfRadStepSize = (degreeStepSize) * Mathf.Deg2Rad / 2f;
float startRad = Mathf.Deg2Rad * startDegree;
float endRad = Mathf.Deg2Rad * endDegree;
//Let's save the vector at the beginning and the one on the end to make a perfectly straight line
vertices[0] = new Vector3(Mathf.Sin(startRad) * outerRadius + xPosition, Mathf.Cos(startRad) * outerRadius + yPosition, 0);
vertices[vertices.Length - 1] = new Vector3(Mathf.Sin(endRad) * innerRadius + xPosition, Mathf.Cos(endRad) * innerRadius + yPosition, 0);
for (int i = 1; i < vertices.Length - 1; i++)
{
//Pure coinsidence that saved some calculatons. Half Step Size is the same as what would needed to be calculated here!
float rad = (i - 1) * halfRadStepSize + startRad;
if (i % 2 == 0)
{
vertices[i].x = Mathf.Sin(rad) * outerRadius + xPosition;
vertices[i].y = Mathf.Cos(rad) * outerRadius + yPosition;
vertices[i].z = 0;
}
else
{
vertices[i].x = Mathf.Sin(rad) * innerRadius + xPosition;
vertices[i].y = Mathf.Cos(rad) * innerRadius + yPosition;
vertices[i].z = 0;;
}
}
mesh.vertices = vertices;
int[] tri = new int[(vertices.Length - 2) * 3];
for (int i = 0; i < (vertices.Length - 2); i++)
{
int index = i * 3;
if (i % 2 == 0)
{
tri[index + 0] = i + 0;
tri[index + 1] = i + 2;
tri[index + 2] = i + 1;
}
else
{
tri[index + 0] = i + 0;
tri[index + 1] = i + 1;
tri[index + 2] = i + 2;
}
}
mesh.triangles = tri;
Vector3[] normals = new Vector3[vertices.Length];
Color[] colors = new Color[vertices.Length];
var f = Vector3.forward;
for (int i = 0; i < vertices.Length; i++)
{
normals[i].x= f.x;
normals[i].y= f.y;
normals[i].z= f.z;
colors[i] = color;
}
mesh.normals = normals;
mesh.colors = colors;
meshFilter.mesh = mesh;
}

How to render edges as cylinders?

I've loaded an OBJ polyhedron and I've used EdgesGeometry() to extract its edges:
var edges = new THREE.LineSegments(new THREE.EdgesGeometry(child.geometry), new THREE.LineBasicMaterial( {color: 0x000000}) );
But I would like to render each edge as a cylinder with configurable radius. Something like this:
A customizable solutuion, which you can start from:
var edgesGeom = new THREE.EdgesGeometry(dodecahedronGeom); //EdgesGeometry is a BufferGeometry
var thickness = 0.25; // radius of a cylinder
for (var i = 0; i < edgesGeom.attributes.position.count - 1; i+=2){
// when you know that it's BufferGeometry, you can find vertices in this way
var startPoint = new THREE.Vector3(
edgesGeom.attributes.position.array[i * 3 + 0],
edgesGeom.attributes.position.array[i * 3 + 1],
edgesGeom.attributes.position.array[i * 3 + 2]
);
var endPoint = new THREE.Vector3(
edgesGeom.attributes.position.array[i * 3 + 3],
edgesGeom.attributes.position.array[i * 3 + 4],
edgesGeom.attributes.position.array[i * 3 + 5]
);
var cylLength = new THREE.Vector3().subVectors(endPoint, startPoint).length(); // find the length of a cylinder
var cylGeom = new THREE.CylinderBufferGeometry(thickness, thickness, cylLength, 16);
cylGeom.translate(0, cylLength / 2, 0);
cylGeom.rotateX(Math.PI / 2);
var cyl = new THREE.Mesh(cylGeom, new THREE.MeshLambertMaterial({color: "blue"}));
cyl.position.copy(startPoint);
cyl.lookAt(endPoint); // and do the trick with orienation
scene.add(cyl);
}
jsfiddle example
Here's a version of #prisoner849's excellent answer which returns a merged BufferGeometry for just the cylinders:
/** Convert an edges geometry to a set of cylinders w/ the given thickness. */
function edgesToCylinders(edgesGeometry, thickness) {
const {position} = edgesGeometry.attributes;
const {array, count} = position;
const r = thickness / 2;
const geoms = [];
for (let i = 0; i < count * 3 - 1; i += 6) {
const a = new THREE.Vector3(array[i], array[i + 1], array[i + 2]);
const b = new THREE.Vector3(array[i + 3], array[i + 4], array[i + 5]);
const vec = new THREE.Vector3().subVectors(b, a);
const len = vec.length();
const geom = new THREE.CylinderBufferGeometry(r, r, len, 8);
geom.translate(0, len / 2, 0);
geom.rotateX(Math.PI / 2);
geom.lookAt(vec);
geom.translate(a.x, a.y, a.z);
geoms.push(geom);
}
return THREE.BufferGeometryUtils.mergeBufferGeometries(geoms);
}
Usage:
const edgesGeom = new THREE.EdgesGeometry(dodecahedronGeom);
const cylindersGeom = edgesToCylinders(edgesGeom, 0.25);
const cylinders = new THREE.Mesh(
cylindersGeom,
new THREE.MeshLambertMaterial({color: "blue"})
);
scene.add(cylinders);
See updated fiddle.

Dynamically change point color in Three.js PointClouod

I'm trying to render a matrix of points in Three.js but I need to treat each particle in the cloud as an individual "pixel" for which I can change the color of each on the fly. I figured out how to basically render the point cloud, and can set the initial color, but cannot figure out how to change the color of each point once it's set.
I'm generating the point cloud like this:
function generateRegularPointcloud( color, width, length ) {
var geometry = new THREE.Geometry();
var numPoints = width * length;
var colors = [];
var k = 0;
for( var i = 0; i < width; i++ ) {
for( var j = 0; j < length; j++ ) {
var u = i / width;
var v = j / length;
var x = u - 0.5;
var y = 0;
var z = v - 0.5;
var v = new THREE.Vector3( x,y,z );
var intensity = ( y + 0.1 ) * 7;
colors[ 3 * k ] = color.r * intensity;
colors[ 3 * k + 1 ] = color.g * intensity;
colors[ 3 * k + 2 ] = color.b * intensity;
geometry.vertices.push( v );
colors[ k ] = ( color.clone().multiplyScalar( intensity ) );
k++;
}
}
geometry.colors = colors;
geometry.computeBoundingBox();
var material = new THREE.PointCloudMaterial( { size: pointSize, vertexColors: THREE.VertexColors } );
var pointcloud = new THREE.PointCloud( geometry, material );
return pointcloud;
}
My basic code is here: http://jsfiddle.net/dg34sbsk/
Any idea how to change each point color separately and dynamically? (Data for the colors will be coming in from a web service).
You can directly change its's value pointclouds[0].geometry.colors=... and after that pointclouds[0].geometry.colorsNeedUpdate=true.
To set each point's color just set the colors's children's value like pointclouds[0].geometry.colors[22]=new THREE.Color("rgb(255,0,0)");.
see this:http://jsfiddle.net/aboutqx/dg34sbsk/2/ .click and you will see the color of one point changes.

Three.js r60 Height Map

So I have a heightmap system which works well enough, however since the THREE.js has updated to r60 which removed the Face4 object, I am having issues.
My code is something like this:
this.buildGeometry = function(){
var geo, len, i, f, y;
geo = new THREE.PlaneGeometry(3000, 3000, 128, 128);
geo.dynamic = true;
geo.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2));
this.getHeightData('heightmap.png', function (data) {
len = geo.faces.length;
for(i=0;i<len;i++){
f = geo.faces[i];
if( f ){
y = (data[i].r + data[i].g + data[i].b) / 2;
geo.vertices[f.a].y = y;
geo.vertices[f.b].y = y;
geo.vertices[f.c].y = y;
geo.vertices[f.d].y = y;
}
}
geo.computeFaceNormals();
geo.computeCentroids();
mesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({color:0xff0000}) );
scene.add(mesh);
});
};
This works well since a pixel represents each face. How is this done now that the faces are all triangulated?
Similarly I use image maps for model positioning as well. Each pixel matches to the respective Face4 and a desired mesh is placed at its centroid. How can this be accomplished now?
I really miss being able to update the library and do not want to be stuck in r59 anymore =[
This approach works fine on the recent versions (tested on r66).
Notice that the genFn returns the height y given current col and row, maxCol and maxRow (for testing purposes, you can of course replace it with a proper array lookup or from a grayscale image... 64x64 determines the mesh resolution and 1x1 the real world dimensions.
var genFn = function(x, y, X, Y) {
var dx = x/X;
var dy = y/Y;
return (Math.sin(dx*15) + Math.cos(dy * 5) ) * 0.05 + 0.025;
};
var geo = new THREE.PlaneGeometry(1, 1, 64, 64);
geo.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2));
var iz, ix,
gridZ1 = geo.widthSegments +1,
gridX1 = geo.heightSegments+1;
for (iz = 0; iz < gridZ1; ++iz) {
for (ix = 0; ix < gridX1; ++ix) {
geo.vertices[ ix + gridX1*iz ].y = genFn(ix, iz, gridX1, gridZ1);
}
}
geo.computeFaceNormals();
geo.computeVertexNormals();
geo.computeCentroids();
var mesh = new THREE.Mesh(
geo,
mtl
);
scene.add(mesh);

Resources