THREE.js Copy from SphereBufferGeometry to BufferGeometry - javascript

I have created a BufferGeometry to store a bunch of triangles (for later manipulation with a vertex shader). The vertice position coordinates and colors were generated randomly and added as arrays inside the bufferGeometry.attributes property. When used to make a mesh it worked fine. here is the code:-
//... Buffer Geometry
triangles = 500;
Bgeometry = new THREE.BufferGeometry();
Bgeometry.dynamic = true;
//... Bvertex_coords
Bvertex_coords = new Float32Array( triangles * 3 * 3 );
for ( var i = 0, l = triangles * 3 * 3; i < l; i += 3 )
{
Bvertex_coords[ i ] = Math.random() - 0.5;//... x
Bvertex_coords[ i + 1 ] = 2*(Math.random() - 0.5);//... y
Bvertex_coords[ i + 2 ] = Math.random() - 0.5;//... z
}
Bgeometry.addAttribute( 'position', new THREE.BufferAttribute( Bvertex_coords, 3 ) );
Next I want to create a similar BufferGeometry but this time the triangles should be initially arranged into a spherical "surface". So I created a "donor" SphereBufferGeometry with the appropriate configuration.
The idea was to then simply copy the relevant data from the donor SphereBufferGeometry into the BufferGeometry.
Here is the code I have been using:-
//... Buffer Geometries
////... new (r72) method - create a "donor" sphereBufferGeometry
var Cgeometry = new THREE.SphereBufferGeometry( 1, 4, 4 );
var num_Cgeom_pos_coords = Cgeometry.attributes.position.array.length;
var num_Cgeom_vertices = num_Cgeom_pos_coords/3;
//...Dgeometry
//... first make a plain buffer geometry
//... then copies data from an existing BufferSphereGeometry
Dgeometry = new THREE.BufferGeometry();
Dgeometry.dynamic = true;
Dpos_coords = new Float32Array( num_Cgeom_pos_coords );
Dpos_normals = new Float32Array( num_Cgeom_pos_coords );
for ( var pc = 0; pc < num_Cgeom_pos_coords ; pc ++ )
{
Dpos_coords[ pc ] = Cgeometry.attributes.position.array[pc];
Dpos_normals[ pc ] = Cgeometry.attributes.normal.array[pc];
}
Dgeometry.addAttribute( 'position', new THREE.BufferAttribute( Dpos_coords, 3 ) );
Dgeometry.addAttribute( 'normal', new THREE.BufferAttribute( Dpos_normals,3 ) );
Pack_of_Triangles_mesh_D = new THREE.Mesh( Dgeometry, Sphere_Ord_material);
For some reason when I copy from the SphereBufferGeometry to a BufferGeometry and make a mesh only a small fraction (about 20-25%?) of the triangles are actually plotted.
QUESTION
So my question is: What do I need to do to get all the triangles to plot?
UPDATE1
By contrast the following method works OK: (1) make a mesh with a SphereGeometry, (2) make a new BufferGeometry with .setFromObject(mesh) (3) copy from the previous BufferGeometry to a new BufferGeometry and use the latter to make a new mesh. But this seems a long-winded approach.
UPDATE2
The following method also works OK when Cgeometry is a SphereBufferGeometry.
Dgeometry = new THREE.BufferGeometry();
Dgeometry.copy ( Cgeometry );
var num_Dgeom_pos_coords = Dgeometry.attributes.position.array.length;
var num_D_geom_vertices = num_D_geom_pos_coords/3;
It seems that the original problem is due to indexing being used in a SphereBufferGeometry and no index being built by default in a new BufferGeometry.
There doesn't seem to be a simple way of copying the index from the SphereBufferGeometry to the new BufferGeometry.
I am a bit confused because I thought the idea of BufferGeomtry involved doing without indexing.

Related

Changing from THREE.Geometry to THREE.BufferGeometry with THREE JS

Hi I'm new to Three JS and need a little help. I am using a piece of code I found to create a sphere of random points but since THREE.Geometry has since been depreciated I need to change THREE.Geometry to THREE.BufferGeometry in the code.
The original code is :
var pointsGeometry = new THREE.Geometry();
for (var i = 0; i < 1000; i++) {
var vector = new THREE.Vector3();
// for simplicity I have omitted script
// that would be placed here for creating random
// values for vector.x, vector.y, vector.z etc etc
pointsGeometry.vertices.push(vector);
}
So now I believe I need to use:
const pointsGeometry = new THREE.BufferGeometry();
but how do I push each vector into an array that exists in an attribute of pointsGeometry named 'vertices'?
In the for loop I cannot use :
pointsGeometry.setAttribute( 'vertices', new THREE.Float32BufferAttribute( vector ) );
I thought I needed to manually create an array 'vectorsArray' and push each vector into the array in the for loop and then add it after the loop
pointsGeometry.setAttribute( 'vertices', new THREE.Float32BufferAttribute( vectorsArray ) );
while this does create a vertices attribute and add an array of 1000, each value is NaN when it should be:
0: Object { x: -21.16441539757467, y: 112.77250047881454, z: -37.63426937227097, … } etc
I have checked that vectorsArray does posses the correct values which they do, but for some reason they are not getting passed into pointsGeometry.setAttribute( 'vertices'). What am I doing wrong please?
Thanks in advance.
Try it like so:
const geometry = new THREE.BufferGeometry();
const points = [];
const point = new THREE.Vector3();
for ( let i = 0; i < 1000; i ++ ) {
point.random();
points.push( point.x, point.y, point.z );
}
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( points, 3 ) );
When using this geometry with THREE.Points, it will create a random point cloud.

Three js how to add triangle to BufferGeometry manually

I've been trying to find the fastest way to change a mesh's vertices with three.js. I found that if I change parts of mesh.geometry.attributes.position.array, then set mesh.geometry.attributes.position.needsUpdate=true, it works well and doesn't have to rebuild arrays or recreate opengl buffers. I found that needsUpdate=true changes the version number of the attribute and that makes it resend the attributes vertices array to the opengl buffer.
So I tried doing that myself instead by calling gl.bindBuffer() then gl.bufferData() but then after doing that every loop for a while it crashes on my call to new Float32Array(). Which is weird because when I check my memory usage I'm only using 4MB right before it crashes. I realize it's not the best way to be deallocating/reallocating the array every loop just to make it slightly bigger when I could be doubling the size of the array when it gets full, but I want to understand why it's crashing when done this way.
https://jsfiddle.net/q1txL19c/3/ Crashes in 20 seconds.
But if I change the if(0) to if(1) it works.
What is three.js doing differently that makes it not crash? Why does new Float32Array() fail when not much javascript memory has been used up according to the profiler?
<!doctype html>
<html>
<body style='margin:0;padding:0'>
<script src="https://threejs.org/build/three.js"></script>
<script>
var camera, scene, renderer, mesh
var triangles = 1
init()
function init()
{
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, .1, 10000)
camera.position.z = 15
scene.add(camera)
var geometry = new THREE.BufferGeometry()
var material = new THREE.MeshBasicMaterial( {side: THREE.FrontSide, transparent:false, vertexColors: THREE.VertexColors} )
mesh = new THREE.Mesh(geometry, material)
var positions = new Float32Array([1,1,0, 0,1,0, 0,0,0])
geometry.addAttribute('position', new THREE.BufferAttribute(positions,3))
var colors = new Float32Array([0,0,1, 0,0,0, 0,0,0])
geometry.addAttribute('color', new THREE.BufferAttribute(colors,3))
scene.add(mesh)
renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setClearColor( 0x6699DD )
document.body.appendChild(renderer.domElement)
loop()
}
function addTriangle(geometry)
{
// Make 3 new vertices, each with x,y,z. 9 total positions.
var newVertices = []
for(var i=0; i<9; i++)
newVertices[i] = Math.random()*10-5
appendArrayToAttribute(geometry.attributes.position, newVertices)
// Make 3 new colors, 1 for each new vertex, each with r,g,b. 9 total slots.
var newColors = []
for(var i=0; i<9; i++)
newColors[i] = Math.random()
appendArrayToAttribute(geometry.attributes.color, newColors)
}
function appendArrayToAttribute(attribute, arrayToAppend)
{
// Make a new array for the geometry to fit the 9 extra positions at the end, since you can't resize Float32Array
try
{
var newArray = new Float32Array(attribute.array.length + arrayToAppend.length)
}
catch(e)
{
console.log(e)
if(!window.alerted)
{
alert("out of memory!? can't allocate array size="+(attribute.array.length + arrayToAppend.length))
window.alerted = true
}
return false
}
newArray.set(attribute.array)
newArray.set(arrayToAppend, attribute.array.length)
attribute.setArray(newArray)
if(0)
{
attribute.needsUpdate = true
}
else
{
// Have the geometry use the new array and send it to opengl.
var gl = renderer.context
gl.bindBuffer(gl.ARRAY_BUFFER, renderer.properties.get(attribute).__webglBuffer)
gl.bufferData(gl.ARRAY_BUFFER, attribute.array, gl.STATIC_DRAW)
}
}
function loop()
{
requestAnimationFrame(loop)
mesh.rotation.x += 0.01
mesh.rotation.y += 0.02
renderer.render(scene, camera)
for(var i=0;i<10;i++)
{
addTriangle(mesh.geometry)
triangles++
}
if(Math.random()<.03)
{
console.log("triangles="+triangles)
var gl = renderer.context
console.log("gl buffer size="+gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE))
}
}
</script>
</body>
</html>
You can add faces to BufferGeometry after the first render, but you must pre-allocate your geometry attribute buffers to be large enough, as they can't be resized.
Also, you will be updating array values, not instantiating new arrays.
You can update the number of faces to render like so:
geometry.setDrawRange( 0, 3 * numFacesToDraw ); // 3 vertices for each face
See this related answer and demo.
three.js r.84

What is the most efficient way to display 4 million 2D squares in a browser?

My display has a resolution of 7680x4320 pixels. I want to display up to 4 million different colored squares. And I want to change the number of squares with a slider. If have currently two versions. One with canvas-fillRect which looks somethink like this:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
for (var i = 0; i < num_squares; i ++) {
ctx.fillStyle = someColor;
ctx.fillRect(pos_x, pos_y, pos_x + square_width, pos_y + square_height);
// set pos_x and pos_y for next square
}
And one with webGL and three.js. Same loop, but I create a box geometry and a mesh for every square:
var geometry = new THREE.BoxGeometry( width_height, width_height, 0);
for (var i = 0; i < num_squares; i ++) {
var material = new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } );
material.emissive = new THREE.Color( Math.random(), Math.random(), Math.random() );
var object = new THREE.Mesh( geometry, material );
}
They both work quite fine for a few thousand squares. The first version can do up to one million squares, but everything over a million is just awful slow. I want to update the color and the number of squares dynamically.
Does anyone has tips on how to be more efficient with three.js/ WebGL/ Canvas?
EDIT1: Second version: This is what I do at the beginning and when the slider has changed:
// Remove all objects from scene
var obj, i;
for ( i = scene.children.length - 1; i >= 0 ; i -- ) {
obj = scene.children[ i ];
if ( obj !== camera) {
scene.remove(obj);
}
}
// Fill scene with new objects
num_squares = gui_dat.squareNum;
var window_pixel = window.innerWidth * window.innerHeight;
var pixel_per_square = window_pixel / num_squares;
var width_height = Math.floor(Math.sqrt(pixel_per_square));
var geometry = new THREE.BoxGeometry( width_height, width_height, 0);
var pos_x = width_height/2;
var pos_y = width_height/2;
for (var i = 0; i < num_squares; i ++) {
//var object = new THREE.Mesh( geometry, );
var material = new THREE.Material()( { color: Math.random() * 0xffffff } );
material.emissive = new THREE.Color( Math.random(), Math.random(), Math.random() );
var object = new THREE.Mesh( geometry, material );
object.position.x = pos_x;
object.position.y = pos_y;
pos_x += width_height;
if (pos_x > window.innerWidth) {
pos_x = width_height/2;
pos_y += width_height;
}
scene.add( object );
}
The fastest way to draw squares is to use the gl.POINTS primitive and then setting gl_PointSize to the pixel size.
In three.js, gl.POINTS is wrapped inside the THREE.PointCloud object.
You'll have to create a geometry object with one position for each point and pass that to the PointCloud constructor.
Here is an example of THREE.PointCloud in action:
http://codepen.io/seanseansean/pen/EaBZEY
geometry = new THREE.Geometry();
for (i = 0; i < particleCount; i++) {
var vertex = new THREE.Vector3();
vertex.x = Math.random() * 2000 - 1000;
vertex.y = Math.random() * 2000 - 1000;
vertex.z = Math.random() * 2000 - 1000;
geometry.vertices.push(vertex);
}
...
materials[i] = new THREE.PointCloudMaterial({size:size});
particles = new THREE.PointCloud(geometry, materials[i]);
I didn't dig through all the code but I've set the particle count to 2m and from my understanding, 5 point clouds are generated so 2m*5 = 10m particles and I'm getting around 30fps.
The highest number of individual points I've seen so far was with potree.
http://potree.org/, https://github.com/potree
Try some demo, I was able to observe 5 millions of points in 3D at 20-30fps. I believe this is also current technological limit.
I didn't test potree on my own, so I cant say much about this tech. But there is data convertor and viewer (threejs based) so should only figure out how to convert the data.
Briefly about your question
The best way handle large data is group them as quad-tree (2d) or oct-tree (3d). This will allow you to not bother program with part that is too far from camera or not visible at all.
On the other hand, program doesnt like when you do too many webgl calls. Try to understand it like this, you want to do create ~60 images each second. But each time you set some parameter for GPU, program must do some sync. Spliting data means you will need to do more setup so tree must not be too detialed.
Last thing, someone said:
You'll probably want to pass an array of values as one of the shader uniforms
I dont suggest it, bad idea. Texture lookup is quite fast, but attributes are always faster. If we are talking about 4M points, you cant afford reading data from uniforms.
Sorry I cant help you with the code, I could do it without threejs, Im not threejs expert :)
I would recommend trying pixi framework( as mentioned in above comments ).
It has webgl renderer and some benchmarks are very promising.
http://www.goodboydigital.com/pixijs/bunnymark_v3/
It can handle allot of animated sprites.
If your app only displays the squares, and doesnt animate, and they are very simple sprites( only one color ) then it would give better performance than the demo link above.

adding external texture to three.js scene object

just getting my hands dirty with three.js and im curious on something that may be relatively simple…
I built a scene in the three.js editor and have figured out how to load the scene. In the editor, I added an image as a map texture but I realize it wont know where it is loaded externally on my server. So I've loaded the scene and objects and lights, but how can I map my textures to the objects?
// MATERIALS
var wireframe = THREE.ImageUtils.loadTexture( 'textures/wireframe.jpg' );
wireframe.wrapS = wireframe.wrapT = THREE.RepeatWrapping;
wireframe.repeat.set( 4, 4 );
var wireframeMaterial = new THREE.MeshLambertMaterial({
map : wireframe,
side : THREE.DoubleSide
});
// LOAD SCENE
var loader = new THREE.ObjectLoader();
loader.load( 'js/scene.js', function ( obj ) {
obj.traverse( function( node ) {
if ( node instanceof THREE.Mesh ) {
node.castShadow = true;
node.receiveShadow = true;
var plane = scene.getObjectByName( "plane", true );
plane.material = wireframeMaterial;
}
});
scene.add( obj );
render();
});
When adding plane.material = wireframeMaterial; into the loader, all my objects just disappear… How can I properly map the wireframeMaterial the plane object?
working example with var plane and plane.material commented out:
http://goo.gl/czSg7P
Scene:
http://goo.gl/BAVgVS
To map the texture you have to create a material using your texture and apply it to your plane.
If necessary you can set repetition of your texture with:
wireframe.wrapS = THREE.RepeatWrapping;
wireframe.wrapT = THREE.RepeatWrapping;
wireframe.repeat.set( 4, 4 );
And then you need to do something like this:
material = new THREE.MeshLambertMaterial({ map : wireframe, side : THREE.DoubleSide });
plane.material = material;
EDIT
You create a scene inside a scene. Since the loader returns a scene and then you add it again to a scene. This will give issues for sure.
Try to replace scene.add( obj ); with this in your loader:
for( var i = 0; i < obj.children.length; i++ ){
scene.add( obj.children[i] );
}
Not sure if that causes the problem, but I made a fiddle and it all seems to work fine for me. The problem is that I can't load external sources in my fiddle so instead I had to use your image as base64 string and the code becomes a bit different.
This means you have to change it a bit to make it work again with an image from your local folder.
If you exchange the Whole //TEXTURE part of the code with the following it should work:
var texture = new THREE.ImageUtils.loadTexture( 'img/wireframe.jpg' );
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 1, 1 );
var textureMaterial = new THREE.MeshLambertMaterial({ map: texture, side : THREE.DoubleSide });

Using multiuple textures on a sphere [Three.js]

Is it possible to load multiple textures on a sphere?
I mean to say is there any way in Three.js to split a sphere into n pieces , texture them separately and render those pieces once again as a whole sphere?
I do not want to load the entire texture on the sphere, instead, only those parts are to be rendered which the user will first see on the screen and as the user rotates the sphere the rest part of the texture must be loaded.
Moreover, when I use a single image on a sphere it seems to converge at poles making it worse.
This should help: https://open.bekk.no/procedural-planet-in-webgl-and-three-js
Instead of using a sphere try using a cube and expanding it into a sphere. Cube logic on the cube sphere will save you a good amount of time.
var geometry = new THREE.BoxGeometry( 1, 1, 1, 8, 8, 8 );
for ( var i in geometry.vertices ) {
var vertex = geometry.vertices[ i ];
vertex.normalize().multiplyScalar(radius);
}
var materialArray = [];
var faceMaterial = new THREE.MeshLambertMaterial({
color: sphereColor,
transparent: true,
opacity: 0.4
});
for (var i = 0; i < 6; i++) {
materialArray.push(faceMaterial);
}
var material = new THREE.MeshFaceMaterial(materialArray);
var sphere = new THREE.Mesh( geometry, material );
scene.add( sphere );

Categories

Resources