I'm using Three.js to create points on a sphere, similar to the periodic table of elements example.
My data set is circles of irregular size, and I wish to evenly distribute them around the surface of a sphere. After numerous hours searching the web, I realize that is much harder than it sounds.
Here are examples of this idea in action:
Vimeo
Picture
circlePack Java applet
Is there an algorithm that will allow me to do this? The packing ratio doesn't need to be super high and it'd ideally be something quick and easy to calculate in JavaScript for rendering in Three.js (Cartesian or Coordinate system). Efficiency is key here.
The circle radii can vary widely. Here's an example using the periodic table code:
Here's a method to try: an iterative search using a simulated repulsive force.
Algorithm
First initialize the data set by arranging the circles across the surface in any kind of algorithm. This is just for initialization, so it doesn't have to be great. The periodic table code will do nicely. Also, assign each circle a "mass" using its radius as its mass value.
Now begin the iteration to converge on a solution. For each pass through the main loop, do the following:
Compute repulsive forces for each circle. Model your repulsive force after the formula for gravitational force, with two adjustments: (a) objects should be pushed away from each other, not attracted toward each other, and (b) you'll need to tweak the "force constant" value to fit the scale of your model. Depending on your math ability you may be able to calculate a good constant value during planning; other wise just experiment a little at first and you'll find a good value.
After computing the total forces on each circle (please look up the n-body problem if you're not sure how to do this), move each circle along the vector of its total calculated force, using the length of the vector as the distance to move. This is where you may find that you have to tweak the force constant value. At first you'll want movements with lengths that are less than 5% of the radius of the sphere.
The movements in step 2 will have pushed the circles off the surface of the sphere (because they are repelling each other). Now move each circle back to the surface of the sphere, in the direction toward the center of the sphere.
For each circle, calculate the distance from the circle's old position to its new position. The largest distance moved is the movement length for this iteration in the main loop.
Continue iterating through the main loop for a while. Over time the movement length should become smaller and smaller as the relative positions of the circles stabilize into an arrangement that meets your criteria. Exit the loop when the movement legth drops below some very small value.
Tweaking
You may find that you have to tweak the force calculation to get the algorithm to converge on a solution. How you tweak depends on the type of result you're looking for. Start by tweaking the force constant. If that doesn't work, you may have to change the mass values up or down. Or maybe change the exponent of the radius in the force calculation. For example, instead of this:
f = ( k * m[i] * m[j] ) / ( r * r );
You might try this:
f = ( k * m[i] * m[j] ) / pow( r, p );
Then you can experiment with different values of p.
You can also experiment with different algorithms for the initial distribution.
The amount of trial-and-error will depend on your design goals.
Here is something you can build on perhaps. It will randomly distribute your spheres along a sphere. Later we will iterate over this starting point to get an even distribution.
// Random point on sphere of radius R
var sphereCenters = []
var numSpheres = 100;
for(var i = 0; i < numSpheres; i++) {
var R = 1.0;
var vec = new THREE.Vector3(Math.random(), Math.random(), Math.random()).normalize();
var sphereCenter = new THREE.Vector3().copy(vec).multiplyScalar(R);
sphereCenter.radius = Math.random() * 5; // Random sphere size. Plug in your sizes here.
sphereCenters.push(sphereCenter);
// Create a Three.js sphere at sphereCenter
...
}
Then run the below code a few times to pack the spheres efficiently:
for(var i = 0; i < sphereCenters.length; i++) {
for(var j = 0; j < sphereCenters.length; j++) {
if(i === j)
continue;
// Calculate the distance between sphereCenters[i] and sphereCenters[j]
var dist = new THREE.Vector3().copy(sphereCenters[i]).sub(sphereCenters[j]);
if(dist.length() < sphereSize) {
// Move the center of this sphere to compensate.
// How far do we have to move?
var mDist = sphereSize - dist.length();
// Perturb the sphere in direction of dist magnitude mDist
var mVec = new THREE.Vector3().copy(dist).normalize();
mVec.multiplyScalar(mDist);
// Offset the actual sphere
sphereCenters[i].add(mVec).normalize().multiplyScalar(R);
}
}
}
Running the second section a number of times will "converge" on the solution you are looking for. You have to choose how many times it should be run in order to find the best trade-off between speed, and accuracy.
You can use the same code as in the periodic table of elements.
The rectangles there do not touch, so you can get the same effect with circles, virtually by using the same code.
Here is the code they have:
var vector = new THREE.Vector3();
for ( var i = 0, l = objects.length; i < l; i ++ ) {
var phi = Math.acos( -1 + ( 2 * i ) / l );
var theta = Math.sqrt( l * Math.PI ) * phi;
var object = new THREE.Object3D();
object.position.x = 800 * Math.cos( theta ) * Math.sin( phi );
object.position.y = 800 * Math.sin( theta ) * Math.sin( phi );
object.position.z = 800 * Math.cos( phi );
vector.copy( object.position ).multiplyScalar( 2 );
object.lookAt( vector );
targets.sphere.push( object );
}
Related
I'm currently going through this tutorial on rendering shapes WebGL (specifically a sphere in this case) and I understand the math behind the generation of each point on the sphere. In the tutorial though, the author defines one method to find all of the vertices and another to generate all of the squares that will comprise the sphere.
A couple of things are unclear from what is done in the tutorial. First, how exactly are the vertices generated by the parametric equation being connected to the squares (triangle strips) being generated? I've made a bare bones program in plain javascript and HTML5 before doing the same thing just using the vertices generated so I'm not seeing how and why they have to be used in conjunction with the triangle strips. The other point of confusion is specifically regarding the function that generates the squares:
var indexData = [];
for (var latNumber = 0; latNumber < latitudeBands; latNumber++) {
for (var longNumber = 0; longNumber < longitudeBands; longNumber++) {
var first = (latNumber * (longitudeBands + 1)) + longNumber;
var second = first + longitudeBands + 1;
indexData.push(first);
indexData.push(second);
indexData.push(first + 1);
indexData.push(second);
indexData.push(second + 1);
indexData.push(first + 1);
}
}
To generate the first point of each square (point on the top left corner) the following is done: var first = (latNumber * (longitudeBands + 1)) + longNumber;
I'm not sure why the number of the lattitude line needs to be multiplied by the total number of longitude lines (plus 1 to fully wrap around) at each step.
The code for both functions is toward the bottom of the tutorial. A general explanation of the use of triangle strips in a case like this could also be helpful, thanks.
how exactly are the vertices generated by the parametric equation being connected to the squares (triangle strips) being generated?
A: Vertices are basically points. So its basically generating points using math. Quote from tutorial :
"for a sphere of radius r, with m latitude bands and n longitude bands, we can generate values for x, y, and z by taking a range of values for θ by splitting the range 0 to π up into m parts, and taking a range of values for φ by splitting the range 0 to 2π into n parts, and then just calculating:
x = r sinθ cosφ
y = r cosθ
z = r sinθ sinφ"
how and why they have to be used in conjunction with the triangle strips
A: they are not triangle STRIPS as in the primitive type gl.TRIANGLE_STRIP, but merely regular triangles defined with 3 points.
regarding the function that generates the squares
A: They are not generation squares per se, but using the points generated from the parametric equation to create triangles for the GPU to render. The code you shown in the OP basically divides a square into 2 triangles.
What is PlaneBufferGeometry exactly and how it is different from PlaneGeometry? (r69)
PlaneBufferGeometry is a low memory alternative for PlaneGeometry. the object itself differs in a lot of ways. for instance, the vertices are located in PlaneBufferGeometry are located in PlaneBufferGeometry.attributes.position instead of PlaneGeometry.vertices
you can take a quick look in the browser console to figure out more differences, but as far as i understand, since the vertices are usually spaced on a uniform distance (X and Y) from each other, only the heights (Z) need to be given to position a vertex.
The main differences are between Geometry and BufferGeometry.
Geometry is a "user-friendly", object-oriented data structure, whereas BufferGeometry is a data structure that maps more directly to how the data is used in the shader program. BufferGeometry is faster and requires less memory, but Geometry is in some ways more flexible, and certain operations can be done with greater ease.
I have very little experience with Geometry, as I have found that BufferGeometry does the job in most cases. It is useful to learn, and work with, the actual data structures that are used by the shaders.
In the case of a PlaneBufferGeometry, you can access the vertex positions like this:
let pos = geometry.getAttribute("position");
let pa = pos.array;
Then set z values like this:
var hVerts = geometry.heightSegments + 1;
var wVerts = geometry.widthSegments + 1;
for (let j = 0; j < hVerts; j++) {
for (let i = 0; i < wVerts; i++) {
//+0 is x, +1 is y.
pa[3*(j*wVerts+i)+2] = Math.random();
}
}
pos.needsUpdate = true;
geometry.computeVertexNormals();
Randomness is just an example. You could also (another e.g.) plot a function of x,y, if you let x = pa[3*(j*wVerts+i)]; and let y = pa[3*(j*wVerts+i)+1]; in the inner loop. For a small performance benefit in the PlaneBufferGeometry case, let y = (0.5-j/(hVerts-1))*geometry.height in the outer loop instead.
geometry.computeVertexNormals(); is recommended if your material uses normals and you haven't calculated more accurate normals analytically. If you don't supply or compute normals, the material will use the default plane normals which all point straight out of the original plane.
Note that the number of vertices along a dimension is one more than the number of segments along the same dimension.
Note also that (counterintuitively) the y values are flipped with respect to the j indices: vertices.push( x, - y, 0 ); (source)
I have a basic particle system in JavaScript (utilising canvas for rendering), and I'm trying to find the best way to handle collisions between particles. The particle system can handle about 70,000 particles at a pretty decent FPS.
It consists of an array that contains every Particle object.
Each Particle object contains 3 Vector objects (one for displacement, velocity, and acceleration) which contain an x and a y variable.
Before each frame, acceleration vectors are applied to velocity vectors, and velocity vectors are applied to displacement vectors for every single Particle object.
The renderer then iterates through each Particle and then draws a 1x1 pixel square at the location of every displacement vector.
The particle system also has 'magnetic' fields also, which can cause the particles to accelerate towards/away from a given point.
I tried applying a 'magnetic' field to each particle, but the calculations I use to get the updated acceleration vectors for each particle are too inefficient, and this method reduced the FPS considerably.
Below is the code I use to recalculate Particle acceleration vectors, with respect to nearby magnetic fields (This function is called before every frame):
Particle.prototype.submitToFields = function (fields) {
// our starting acceleration this frame
var totalAccelerationX = 0;
var totalAccelerationY = 0;
// for each passed field
for (var i = 0; i < fields.length; i++) {
var field = fields[i];
// find the distance between the particle and the field
var vectorX = field.point.x - this.point.x;
var vectorY = field.point.y - this.point.y;
// calculate the force via MAGIC and HIGH SCHOOL SCIENCE!
var force = field.mass / Math.pow(vectorX*vectorX+vectorY*vectorY,1.5);
// add to the total acceleration the force adjusted by distance
totalAccelerationX += vectorX * force;
totalAccelerationY += vectorY * force;
}
// update our particle's acceleration
this.acceleration = new Vector(totalAccelerationX, totalAccelerationY);
}
It's obvious why the above method reduced the performance drastically - the number of calculations rises exponentially with every new particle added.
Is there another method of particle collision detection that will have good performance with thousands of particles? Will these methods work with my current object structure?
Don't create a new Vector here. It means that you're creating 70 000 new Vectors each frame. Just change the vector values :
this.acceleration.x = totalAccelerationX; // or : this.acceleration[0] = totalAccelerationX;
this.acceleration.y = totalAccelerationY; // or : this.acceleration[1] = totalAccelerationY;
If it doesn't helps enough, you'll have to use a WebWorker.
[EDIT: see this jsfiddle for a live example plus accompanying code]
Using three.js I'm trying to render out some celestial bodies with prominent features.
Unfortunately no examples are provided on how to apply spherical heightmaps with threejs but they do have an example where a heightmap is applied to a plane.
I took said example and modified it to use a SphereGeometry(); instead of a PlaneGeometry();
Obviously the geometry of a sphere is critically different from that of a plane, and when rendering out the results the sphere shows as a flat piece of texture.
The heightmap code for planes:
var plane = new THREE.PlaneGeometry( 2000, 2000, quality - 1, quality - 1 );
plane.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
for ( var i = 0, l = plane.vertices.length; i < l; i ++ ) {
var x = i % quality, y = ~~ ( i / quality );
plane.vertices[ i ].y = data[ ( x * step ) + ( y * step ) * 1024 ] * 2 - 128;
}
Now I'm guessing the solution is relatively simple: instead of mapping to the plane's 2d coordinate in the for loop, it has to find the surface coordinate of the sphere in 3d space. Unfortunately I'm not really a pro at 3d maths so I'm pretty much stuck at this point.
An example of the heightmap applied to the sphere and all code is put together in this jsfiddle. An updated jsfiddle shows an altered sphere but with random data instead of the height map data.
I know for a fact you can distort the sphere's 3d points to generate these surface details, but I'd like to do so using a heightmap. This JSFiddle is as far as I got- it'll randomly alter points to give a rocky appearance to the sphere, but obviously doesnt look very natural.
EDIT: The following is the logic required I wish to implement that maps heightmap data to a sphere.
In order to map the data to a sphere, we will need to map coordinates from a simple spherical coordinate system (longitude φ, latitude θ, radius r) to Cartesian coordinates (x, y, z). Just as in normal height-mapping the data value at (x, y) is mapped to z, we will map the value at (φ, θ) to r. This transformation comes down to:
x = r × cos φ × sin θ
y = r × sin φ × sin θ
z = r × cos θ
r = Rdefault + Rscale × d(φ, θ)
The parameters Rdefault and Rscale can be used to control the size of the sphere and the height map on it.
Uses vector3 to move each vertices:
var vector = new THREE.Vector3()
vector.set(geometry.vertices[i].x, geometry.vertices[i].y, geometry.vertices[i].z);
vector.setLength(h);
geometry.vertices[i].x = vector.x;
geometry.vertices[i].y = vector.y;
geometry.vertices[i].z = vector.z;
Example: http://jsfiddle.net/damienlabat/b3or4up3/
If you want to apply a 2D map onto the 3D sphere surface, you will need to use UVs of the sphere. Fortunately, UVs come with THREE.SphereGeometry by default.
The UVs are stored per-face though, so you will need to iterate through the faces array.
For each face in the geometry:
Read the corresponding UV value in the FaceVertexUvs array for each associated vertex.
Read the height map value using that UV location.
Shift the vertex along the vertex normal by that value. The faces array gives the vertex index, which you can use to index into the vertices array to get/set the vertex position.
After this is all done, set verticesNeedUpdate to true to update the vertices.
I am working on a project in javascript involving google maps.
The goal is to figure out 16-20 coordinate points within n kilometers from a set of latitude longitude coordinates such that the 16 points if connected will form a circle around the original coordinates.
The end goal is to make it so I can figure out coordinates to plot and connect on google maps to make a circle around a given set of coordinates.
The code would go something like:
var coordinates = Array();
function findCoordinates(lat, long, range) {
}
coordinates = findCoordinates(-20, 40, 3);
Now to make the magic happen in the findCoordinates() function.
Basically what you're trying to do is find N points on the radius of a circle from a given point with a given radius. One simple way of doing it is splitting the 360 degrees of a circle in to N equal chunks, and finding the points at regular intervals.
The following should do roughly what you're after -
function findCoordinates(lat, long, range)
{
// How many points do we want? (should probably be function param..)
var numberOfPoints = 16;
var degreesPerPoint = 360 / numberOfPoints;
// Keep track of the angle from centre to radius
var currentAngle = 0;
// The points on the radius will be lat+x2, long+y2
var x2;
var y2;
// Track the points we generate to return at the end
var points = [];
for(var i=0; i < numberOfPoints; i++)
{
// X2 point will be cosine of angle * radius (range)
x2 = Math.cos(currentAngle) * range;
// Y2 point will be sin * range
y2 = Math.sin(currentAngle) * range;
// Assuming here you're using points for each x,y..
p = new Point(lat+x2, long+y2);
// save to our results array
points.push(p);
// Shift our angle around for the next point
currentAngle += degreesPerPoint;
}
// Return the points we've generated
return points;
}
The array of points you get back can then easily be used to draw the circle you wish on your google map.
If your overall goal however is just to draw a circle at a fixed radius around a point, then a far easier solution may be to use an overlay. I've found KMBox to be very easy to set up - you give it a central point, a radius and an image overlay (in your case, a transparent circle with a visible line around the edge) and it takes care of everything else, including resizing it on zoom in/out.
I had to find some code to calculate Great Circle distances a while back (just Google "Great Circle" if you don't know what I'm talking about) and I found this site:
http://williams.best.vwh.net/gccalc.htm
You might be able to build up your own JavaScript code to do your lat/lon range calculations using the JavaScript from that site as a reference. It sounds to me like you just need to divide up the 360 degrees of a circle into an equal number of pieces and draw a line out to an equal distance from the center at each "bearing". Once you know the lat/lon at the other end of each bearing/distance line, then connecting the dots to form a polygon is trivial.