BufferGeometry indices issue - javascript

I'm trying to port some code that I made in openFrameworks into THREE.JS. The code generates a landscape using perlin noise. I made it so that first a static index array is created, and then the positions of the vertices are placed out in a square grid each displaced by a specified distance. This is so that the positions of the vertices within the array can be shifted (up, down, left or right) so that when the camera moves the landscape can be updated and new strip of landscape generated based on the direction of the camera movement.
For each vertex, 6 indices are added to the index array which refer to two adjacent triangles. I didn't want to waste memory storing duplicates of each vertex for each triangle.
For example e.g.
v12 v11 v10
*----*----*
|\ |\ |
| \ | \ |
| \ | \ |
| \| \|
*----*----*
v02 v01 v00
so e.g. at vertex v00, the indices {v00, v10, v11} and {v00, v11, v01} are added, etc.
In my openFrameworks code, this all works perfectly! After a lot of trouble I finally got things working in THREE.js, but have noticed that as soon as I increase the amount of vertices everything starts getting weird - the triangles are connecting (what seems) all over the place, and a large chunk of vertices start to become skipped. At the moment anything up to and including a grid size of 256*256 works fine, but as soon as I increase any higher I start to see all the artefacts.
I'm thinking this is probably an issue with offsetting, but I don't really understand what this means, or how to implement it with my code. I've seen other people use it successfully when they define their indices in order (0, 1, 2, 3, ... ) and instead use 3 individual vertices for each triangle (and add each three vertices for each triangle in sequence). I can't seem to get the same kind of thing to work.
Any ideas? I've got my code below just in case it helps. You can see the parts where I commented out the ofsetting.
var landscape =
{
size: 0,
chunkSize: 21845,
distance: 0,
geometry: null,
mesh: null,
positions: null,
normals: null,
colors: null,
indices: null,
generateVertex: function( r, c )
{
var pos, color;
// Set position
pos = new THREE.Vector3();
pos.x = this.distance * c;
pos.z = this.distance * r;
pos.y = -2 + 5*simplex.noise2D( 0.1*pos.x, 0.1*pos.z );
// Set color
color = new THREE.Color();
color.setRGB( Math.random(1), 0, 0 );
this.vertices.setXYZ( r * this.size + c, pos.x, pos.y, pos.z );
this.colors.setXYZ( r * this.size + c, color.r, color.g, color.b );
},
generateIndices: function( i, r, c )
{
this.indices[ i ] = ( r * this.size ) + c;
this.indices[ i + 1 ] = ( ( r + 1 ) * this.size ) + c;
this.indices[ i + 2 ] = ( ( r + 1 ) * this.size ) + ( c + 1 );
this.indices[ i + 3 ] = ( r * this.size ) + c;
this.indices[ i + 4 ] = ( ( r + 1 ) * this.size ) + ( c + 1 );
this.indices[ i + 5 ] = ( r * this.size ) + ( c + 1 );
/*this.indices[ i ] = ( ( r * this.size ) + c ) % ( 3 * this.chunkSize );
this.indices[ i + 1 ] = ( ( ( r + 1 ) * this.size ) + c ) % ( 3 * this.chunkSize );
this.indices[ i + 2 ] = ( ( ( r + 1 ) * this.size ) + ( c + 1 ) ) % ( 3 * this.chunkSize );
this.indices[ i + 3 ] = ( ( r * this.size ) + c ) % ( 3 * this.chunkSize );
this.indices[ i + 4 ] = ( ( ( r + 1 ) * this.size ) + ( c + 1 ) ) % ( 3 * this.chunkSize );
this.indices[ i + 5 ] = ( ( r * this.size ) + ( c + 1 ) ) % ( 3 * this.chunkSize ); */
},
generatePoint: function( x, z )
{
},
generate: function( size, distance )
{
var sizeSquared, i;
sizeSquared = size * size;
i = 0;
this.size = size;
this.distance = distance;
// Create buffer geometry
this.geometry = new THREE.BufferGeometry();
this.indices = new Uint16Array( 6*(size-1)*(size-1) );
this.vertices = new THREE.BufferAttribute( new Float32Array( sizeSquared * 3 ), 3 );
this.colors = new THREE.BufferAttribute( new Float32Array( sizeSquared * 3 ), 3 );
// Generate points
for( var r = 0; r < size; r = r + 1 )
{
for( var c = 0; c < size; c = c + 1 )
{
this.generateVertex( r, c );
if( (r < size - 1) && (c < size - 1) )
{
this.generateIndices( i, r, c );
i = i + 6;
}
}
}
// Set geometry
this.geometry.addAttribute( 'index', new THREE.BufferAttribute( this.indices, 1 ) );
this.geometry.addAttribute( 'position', this.vertices );
this.geometry.addAttribute( 'color', this.colors );
//
/*this.geometry.offsets = [];
var triangles = 2 * ( size - 1 ) * ( size - 1 );
var offsets = triangles / this.chunkSize;
for( var j = 0; j < offsets; j = j + 1 )
{
var offset =
{
start: j * this.chunkSize * 3,
index: j * this.chunkSize * 3,
count: Math.min( triangles - ( j * this.chunkSize ), this.chunkSize ) * 3
};
this.geometry.offsets.push( offset );
}*/
var material = new THREE.MeshBasicMaterial( {vertexColors: THREE.VertexColors} );
//var material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors });
this.geometry.computeBoundingSphere();
this.mesh = new THREE.Mesh( this.geometry, material );
scene.add( this.mesh );
}

WebGL is based on OpenGL ES 2.0 which does not support 32 bit index buffers, so as soon as you have more than 256 * 256 vertices, the index buffer can no longer address them all.
From the OpenGL ES 2.0 Standard (section 2.8 Vertex Arrays):
Indexing support with ubyte and ushort indices is supported. Support
for uint indices is not required by OpenGL ES 2.0. If an
implementation supports uint indices, it will export the OES element
index - uint extension.
Assuming that's the issue you can enable 32bit index buffers by getting and checking for the OES_element_index_uint extension.
var uintExt = gl.getExtension("OES_element_index_uint");
if (!uintExt) {
alert("Sorry, this app needs 32bit indices and your device or browser doesn't appear to support them");
return;
}
According to webglstats.com 93.5% of machines support the extension.
You will need to change your generate function to create a 32 bit array:
this.indices = new Uint16Array( 6*(size-1)*(size-1) );
should be:
this.indices = new Uint32Array( 6*(size-1)*(size-1) );
I had a quick delve inside the source of three.js's renderer, and it looks like it checks the type of the index array and will pass gl.UNSIGNED_INT to glDrawElements if you use a Uint32Array.

Related

Applying skeleton to procedural skinned mesh

In this fiddle I'm trying to create a procedural worm (or snake or caterpillar) character, based on the basic bones example at threejs.org/docs/scenes/bones-browser, but with an extra four bones and two spheres for eyes added on; the added/altered code shown here...
function eyeBall() {
function eyeBall( ) {
var faceIndices = [ 'a', 'b', 'c' ];
var color, f, i, j, p, vertexIndex, radius = 1;
var geometry = new THREE.SphereGeometry( radius, 18, 5 );
for ( i = 0; i < geometry.faces.length; i ++ ) {
f = geometry.faces[ i ];
for( j = 0; j < 3; j++ ) {
vertexIndex = f[ faceIndices[ j ] ];
p = geometry.vertices[ vertexIndex ];
f.color = new THREE.Color( 0xffffff );
if( p.y < -0.9 ) {
f.color = new THREE.Color( 0x000000 );
}
}
}
return geometry;
}
function createBones ( sizing ) {
function createBones ( sizing ) {
bones = [];
var prevBone = new THREE.Bone();
bones.push( prevBone );
prevBone.position.z = - sizing.halfLength;
for ( var i = 0; i < sizing.segmentCount; i ++ ) {
var bone = new THREE.Bone();
bone.position.z = sizing.segmentLength;
bones.push( bone );
prevBone.add( bone );
prevBone = bone;
}
//add more bones
var lHdBone = new THREE.Bone();
lHdBone.position.set( 1.4, 0, 2.8 );
lHdBone.name = 'lHdBone';
bones.push( lHdBone );
prevBone.add( lHdBone );
var rHdBone = new THREE.Bone();
rHdBone.position.set( -1.4, 0, 2.8 );
rHdBone.name = 'rHdBone';
bones.push( rHdBone );
prevBone.add( rHdBone );
var lEyeBone = new THREE.Bone();
lEyeBone.position.set( 0, 0, 1 );
lEyeBone.name = 'lEyeBone';
bones.push( lEyeBone );
lHdBone.add( lEyeBone );
var rEyeBone = new THREE.Bone();
rEyeBone.position.set( 0, 0, 1 );
rEyeBone.name = 'rEyeBone';
bones.push( rEyeBone );
rHdBone.add( rEyeBone );
return bones;
}
function initBones () {
function initBones () {
var segmentLength = 8;
var segmentCount = 4;
var length = segmentLength * segmentCount;
var halfLength = length * 0.5;
var sizing = {
segmentLength : segmentLength,
segmentCount : segmentCount,
length : length,
halfLength : halfLength
};
var geometry = createGeometry( sizing );
var bones = createBones( sizing );
mesh = createMesh( geometry, bones );
var lEye = eyeBall();
var rEye = eyeBall();
lEye.rotateX( -Math.PI * 0.5);
rEye.rotateX( -Math.PI * 0.5);
lEye.translate( 1.4, 0, sizing.halfLength + 2.8 );
rEye.translate( -1.4, 0, sizing.halfLength + 2.8 );
var lEyeMesh = new THREE.Mesh( lEye, material );
var rEyeMesh = new THREE.Mesh( rEye, material );
mesh.add( lEyeMesh );
mesh.add( rEyeMesh );
mesh.scale.multiplyScalar( 1 );
scene.add( mesh );
}
The animation in the fiddle moves the spheres in the same general direction but the spheres are clearly not tied to the skeleton as desired. The eyes should be looking forward throughout. I haven't found any comparable questions for this since most modelling is done in Blender, but I am sure this should be possible in three.js alone.
I feel I'm missing something simple!
This is another fiddle with merged geometries. Now one eye seems to act correctly, but the other eye is evidently a child bone; i.e. bones[6] is a child of bones[5], while they should both be children of bones[4].

three.js fails to render the normals in a THREE.Points Geometry

I am quite new to threejs. I am currently working on a project that needs to render a point cloud using three.js via the Qt5 Canvas3D. According to the examples of threejs.org, I use a BufferGeometry and set its attributes(position and normal). Then I use a THREE.Points and THREE.PointsMaterial to wrap it. The result is that I can render the points in the scene, however, the normals set on each vertex seem to be ignored. The code snippet is shown below:
var vertexPositions = [
[10, 10, 0, 1, 0, 0],
[10, -10, 0, 1, 0, 0],
[-10, -10, 0, 1, 0, 0]
];
geometry = new THREE.BufferGeometry();
var vertices = new Float32Array( vertexPositions.length * 3 );
for ( var i = 0; i < vertexPositions.length; i++ )
{
vertices[ i*3 + 0 ] = vertexPositions[i][0];
vertices[ i*3 + 1 ] = vertexPositions[i][1];
vertices[ i*3 + 2 ] = vertexPositions[i][2];
}
var normals = new Float32Array( vertexPositions.length * 3 );
for ( i = 0; i < vertexPositions.length; i++ )
{
normals[ i*3 + 0 ] = vertexPositions[i][3];
normals[ i*3 + 1 ] = vertexPositions[i][4];
normals[ i*3 + 2 ] = vertexPositions[i][5];
}
var colors = new Float32Array( vertexPositions.length * 3 );
for ( i = 0; i < vertexPositions.length; i++ )
{
colors[ i*3 + 0 ] = 1;
colors[ i*3 + 1 ] = 0;
colors[ i*3 + 2 ] = 0;
}
geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
geometry.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
material = new THREE.PointsMaterial({size:50, vertexColors:THREE.VertexColors});
mesh = new THREE.Points(geometry, material);
scene.add(mesh);
How to render the point cloud with normals set on vertices? What am I missing? Any suggestions would be appreciated. Thanks!
You want to render a point cloud and have it interact with lights.
To do so, you must create a custom ShaderMaterial.
In this answer you will find an example of a custom ShaderMaterial that is used with THREE.Points.
three.js r.75

How to detect if a user has drawn a circle on a touch device using canvas and javascript?

I am creating a Tangram puzzle game using Javascript. And I need to detect when a user has drawn a circle (or circle like shape) with their finger. I have been able to gather hundreds (if not thousands) of x and y points with:
var touchX = event.targetTouches[0].pageX - canvas.offsetLeft;
var touchY = event.targetTouches[0].pageY - canvas.offsetTop;
I then push each x and y coordinate into an array:
touchMoveX.push(touchX);
touchMoveY.push(touchY);
I then loop through each array and create two points:
for(var i = 0; i < touchMoveX.length; i++)
{
for(var l=0; l < touchMoveY.length; l++)
{
var xPosition = touchMoveX[i];
var yPosition = touchMoveY[l];
var v1x = touchMoveX[i];
var v2x = touchMoveX[i + 1];
var v1y = touchMoveY[l];
var v2y = touchMoveY[l + 1];
Then using those two points, I use the following formula to figure out the angle between these two points in degrees:
var v1 = {x: v1x, y: v1y}, v2 = {x: v2x, y: v2y},
angleRad = Math.acos( (v1.x * v2.x + v1.y * v2.y) /
(Math.sqrt(v1.x*v1.x + v1.y*v1.y) * Math.sqrt(v2.x*v2.x + v2.y*v2.y) ) ),
angleDeg = angleRad * 180 / Math.PI;
I then sum up all of the angles and see if they are around 360 degrees.
But the above code I have described isn't working very well. Does someone out there have a better way to do this? Thank you very much.
yeah compute the average of all points (giving you a cheaply approximated center) then check if more than a certain percent of points are within a certain threshold. You can tune those values to adjust the precision until it feels right.
edit: Didn't consider that the circle could have multiple sizes, but you could just add another step computing the average of all distances. Adjusted the example for that.
var totalAmount = touchMoveX.length;
// sum up all coordinates and divide them by total length
// the average is a cheap approximation of the center.
var averageX = touchMoveX.reduce( function ( previous, current) {
return previous + current;
} ) / totalAmount ;
var averageY = touchMoveY.reduce( function ( previous, current) {
return previous + current;
} ) / totalAmount ;
// compute distance to approximated center from each point
var distances = touchMoveX.map ( function ( x, index ) {
var y = touchMoveY[index];
return Math.sqrt( Math.pow(x - averageX, 2) + Math.pow(y - averageY, 2) );
} );
// average of those distance is
var averageDistance = distances.reduce ( function ( previous, current ) {
return previous + current;
} ) / distances.length;
var min = averageDistance * 0.8;
var max = averageDistance * 1.2;
// filter out the ones not inside the min and max boundaries
var inRange = distances.filter ( function ( d ) {
return d > min && d < max;
} ).length;
var minPercentInRange = 80;
var percentInRange = inRange.length / totalAmount * 100;
// by the % of points within those boundaries we can guess if it's circle
if( percentInRange > minPercentInRange ) {
//it's probably a circle
}

Three.js mesh based on BufferGeometry not appearing

I'm working on a WebGL game using Three.js & I've decided to switch to a THREE.BufferGeometry implementation from my (working) regular THREE.Geometry solution. I'm messing something up, because the mesh does not draw. I've given the relevant parts of my code below. If I switch to a regular geometry, everything works fine.
It's a voxel based game and I've pre-created each face of each cube as a regular THREE.Geometry. The positionVertices function takes the vertices and faces from each face geometry, positions them so that they correspond to the voxel, and generates the buffer data for the THREE.BufferGeometry. There are no errors or warnings, the final mesh just doesn't appear. I suspect my problem has less to do with Three.js and more with my limited understanding of 3D graphics programming. My best guess right now is that it has something to do with the indexes not being correct. If I remove the indexes, the object appears, but half of the triangles have their normals in the opposite direction.
Chunk.prototype.positionVertices = function( position, vertices, faces, vertexBuffer, indexBuffer, normalBuffer, colorBuffer ) {
var vertexOffset = vertexBuffer.length / 3;
for( var i = 0; i < faces.length; ++i ) {
indexBuffer.push( faces[i].a + vertexOffset );
indexBuffer.push( faces[i].b + vertexOffset );
indexBuffer.push( faces[i].c + vertexOffset );
normalBuffer.push( faces[i].vertexNormals[0].x );
normalBuffer.push( faces[i].vertexNormals[0].y );
normalBuffer.push( faces[i].vertexNormals[0].z );
normalBuffer.push( faces[i].vertexNormals[1].x );
normalBuffer.push( faces[i].vertexNormals[1].y );
normalBuffer.push( faces[i].vertexNormals[1].z );
normalBuffer.push( faces[i].vertexNormals[2].x );
normalBuffer.push( faces[i].vertexNormals[2].y );
normalBuffer.push( faces[i].vertexNormals[2].z );
}
var color = new THREE.Color();
color.setRGB( 0, 0, 1 );
for( var i = 0; i < vertices.length; ++i ) {
vertexBuffer.push( vertices[i].x + position.x );
vertexBuffer.push( vertices[i].y + position.y );
vertexBuffer.push( vertices[i].z + position.z );
colorBuffer.push( color.r );
colorBuffer.push( color.g );
colorBuffer.push( color.b );
}
};
// This will need to change when more than one type of block exists.
Chunk.prototype.buildMesh = function() {
var cube = new THREE.Mesh();
var vertexBuffer = []; // [0] = v.x, [1] = v.y, etc
var faceBuffer = [];
var normalBuffer = [];
var colorBuffer = [];
for( var k = 0; k < this.size; ++k )
for( var j = 0; j < this.size; ++j )
for( var i = 0; i < this.size; ++i ) {
// Iterates over all of the voxels in this chunk and calls
// positionVertices( position, vertices, faces, vertexBuffer, indexBuffer, normalBuffer, colorBuffer ) for each face in the chunk
}
var bGeo = new THREE.BufferGeometry();
bGeo.attributes = {
index: {
itemSize: 1,
array: new Uint16Array( faceBuffer ),
numItems: faceBuffer.length
},
position: {
itemSize: 3,
array: new Float32Array( vertexBuffer ),
numItems: vertexBuffer.length
},
normal: {
itemSize: 3,
array: new Float32Array( normalBuffer ),
numItems: normalBuffer.length
},
color: {
itemSize: 3,
array: new Float32Array( colorBuffer ),
numItems: colorBuffer.length
}
}
var mesh = new THREE.Mesh( bGeo, VOXEL_MATERIALS["ROCK"]);
return mesh;
}
I needed to set a single offset on the geometry.
bGeo.offsets = [
{
start: 0,
index: 0,
count: faceBuffer.length
}
];
Fixed it. The triangles are still displaying wrong, so I guess the faces are messed up, but I can figure that out easily enough.

Weird results combining colours

I'm currently working on a horizontal blur algorithm in javascript, though I doubt the language matters.
I get the data from a canvas which is basically a huge array where every four (RGBA) values stand for one pixel. A value can contain an int ranging from 0 to 255.
When I blur the image, the area's between two different colours turn into strange colours! I've drawn a red rectangle on a black background. Using the algorithm below, I get the following result (4px size):
Though when a use a 1 or 2 pixel size, everything seems to work normally.
Please note this is somewhat messy build up. I'm planning to make this all OOP!
// s: size
// w: width
// h: height
function blur( s, w, h ) {
var src = ctx.getImageData( 0, 0, w, h ); // get imagedata from source
var dst = ctx.createImageData( w, h ); // create imagedata for dest
var x, y, xo, index, rgb; // predefine vars
// loop through y axis
for( y = 0; y < h; y++ ) {
// loop through x axis
for( x = 0; x < w; x++ ) {
rgb = 0; // set total to 0
// loop through area around current pixel
for( xo = 0 - s; xo <= s; xo++ ) {
// get specific index
index = getIndex( x + xo, y, w );
// add nothing if the value doesn't exist (borders)
// if( isNaN( src.data[index] ) ) continue;
if( typeof src.data[index] === 'undefined' ) continue;
// add the values to total
rgb += ( src.data[index] << 16 ) + ( src.data[index + 1] << 8 ) + src.data[index + 2];
}
// get the average of all pixels in that area
rgb = rgb / ( s * 2 + 1);
// get index of current pixel
index = getIndex( x, y, w );
// set pixel in dest
dst.data[index] = ( rgb & 0xff0000 ) >> 16; // red
dst.data[index + 1] = ( rgb & 0x00ff00 ) >> 8; // green
dst.data[index + 2] = ( rgb & 0x0000ff ); // blue
dst.data[index + 3] = 255; // alpha
}
}
// add the new image data
ctx.putImageData( dst, 0, 0 );
}
function getIndex( x, y, w ) {
// calculate the appropriate index, since every pixel has 4 array values
return ( y * ( w * 4 ) + ( x * 4 ) );
}
So what is wrong with my algorithm? I'm a bit lost. Please note that I'm not looking for existing objects/libraries/files for canvas blurring. I like to reinvent everything to educate myself.
Edit: I also like to add that the values I get back are truly the values that represent the colours shown on the canvas. That means that's definitely a miscalculation in my algorithm.
You should average your channels separately. Dividing a packed three-channel value is unlikely to keep each channel within its byte.
The average between 0x030000 (dark red) and 0x000000 (black) becomes 0x018000, which gets a lot of green (0x80)
You should average the channels separately.

Categories

Resources