Полюшко-поле

1. 1. code for a simulation
	2. please do not modify this page

/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*

```
* A roughly spherical cloud of particles collapsing
* and coalescing under its own gravity to form a star.
* I've implemented one of Sal's suggestions, so the
* initial cloud now starts the simulation spinning.
* The system now evolves more slowly, but a lot more
* interestingly. A sort of disc is created and there
* are often several small systems of orbits orbiting
* each other. You can often get planets orbiting the sun
* which have satellites of their own.
* Use the mouse to rotate the universe
* Move the mouse to the bottom of the screen for more controls.
* The red line shows the normal to the mean angular momentum,
* centred on the centre of mass of the universe.
* The histogram shows the distribution of particle masses
* on a log base 2 scale. The final column is the count of
* all particle > 2048 mass units.
* The most distant particle is the one furthest from the
* centre of the universe's mass.
* Note that scaling does no affect particle size to make
* it easier to follow particles at extreme scales.

```

* + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + - * + /

// Reduce this number if simulation is too slow.
var numParticles = 250;
var initialMassRange = [2, 12];
var initialMaxDistance = 600;

// This gives the cloud some initial rotation
var startWithRotation = true;
var initialSpin = 1.8;

var GRAVITATIONAL\_CONSTANT = 0.8;

// How close particles have to be for a collision
var collisionThreshold = 1.5;

// How much mass a particle needs for it to become a star
var sunThreshold = 500;

// Display variables
var backgroundColour = color(10, 10, 20);
var particleColour = color(240, 240, 220);
var sunColour = color(255, 153, 0);
var myFont = createFont("times", 12);
var angularMomentumLineHeight = 120;

var toolbarHeight = 80;
var sliderX = 20;
var sliderY = 350;
var sliderWidth = 80;
var buttonY = 365;

var rotateRate = 1; // Speed of rotation using mouse

var running = true;
var mouseOverButton = false;
var currentScale = 3;
var colourScale = initialMaxDistance \* currentScale;
var maxScale = 100;
var centreX = 200;
var centreY = 200;

// These values are filled in later
var totalMass = 0;
var maxMass = 0;
var massHistogram = [];

for (var h=0; h<12; h++) {

```
   massHistogram.push(0);

```

}

// Take the logarithm base two of the mass
var binMass = function(mass) {

```
   for (var i=massHistogram.length-1; i>=0; i--){
       if (mass > pow(2, i)) {
           return i;
       }
   }

```

};

var Particle = function(x, y, z, v, m) {

```
   this.position = [x, y, z];
   this.velocity = v;

   this.mass = m;
   this.combineWith = [];

   // Radius is the cube root of mass
   this.getRadius = function() {
       this.radius = pow(this.mass, 1/3);
   };

   this.getRadius();

   this.draw = function() {
       var c;
       if (this.mass < sunThreshold) {
           // Particles are darker the further away they are
           var d = constrain((this.position[2] + 0.5 * colourScale) /
               colourScale, 0.05, 1);
           c = lerpColor(backgroundColour, particleColour, d);
       } else {
           c = sunColour;
       }
       fill(c);

       var r = this.radius * 2 * currentScale / log(currentScale);

       if (r < 1) {
           point(this.position[0], this.position[1]);
       } else {
           ellipse(this.position[0], this.position[1], r, r);
       }
   };

   this.move = function() {
       this.position[0] += this.velocity[0];
       this.position[1] += this.velocity[1];
       this.position[2] += this.velocity[2];
   };

   this.attract = function(that) {
       var dx = this.position[0] - that.position[0];
       var dy = this.position[1] - that.position[1];
       var dz = this.position[2] - that.position[2];
       var d2 = dx * dx + dy * dy + dz * dz;
       var d = sqrt(d2);

       if (d<(this.radius+that.radius)*collisionThreshold) {
           this.combineWith.push(that);
           return;
       }

       var force = GRAVITATIONAL_CONSTANT * this.mass*that.mass / d2;
       var accel1 = force / this.mass;
       var accel2 = force / that.mass;

       dx /= d;
       dy /= d;
       dz /= d;

       this.velocity[0] -= accel1 * dx;
       this.velocity[1] -= accel1 * dy;
       this.velocity[2] -= accel1 * dz;

       that.velocity[0] += accel2 * dx;
       that.velocity[1] += accel2 * dy;
       that.velocity[2] += accel2 * dz;
   };

   this.mergeWith = function(that) {
       var mass2 = this.mass + that.mass;
       var proportion = this.mass / mass2;

       for (var i=0; i<3; i++) {
       this.position[i] = this.position[i] * proportion +
                          that.position[i] * (1 - proportion);
       this.velocity[i] = this.velocity[i] * proportion +
                          that.velocity[i] * (1 - proportion);
       }

```

```
       // Update histogram
       massHistogram[binMass(this.mass)]--;
       massHistogram[binMass(mass2)]++;

```

```
       this.mass = mass2;
       if (this.mass > maxMass) { maxMass = this.mass; }

       this.getRadius();
       this.combineWith = [];

```

```
   };

```

};

var initialiseParticles = function(n) {

```
   var particles = [];
   var massRange = initialMassRange[1]-initialMassRange[0];
   totalMass = 0;

   for (var i=0; i<n; i++) {
       // Randomly distribute within sphere
       var phi = random() * 360;
       var theta = acos( random() * 2 - 1 );
       var r = initialMaxDistance * pow(random(), 1/3);
       var x = r * sin(theta) * cos(phi);
       var y = r * sin(theta) * sin(phi);
       var z = r * cos(theta);

       var v;
       if (startWithRotation) {
           var d = initialSpin / sqrt(x*x + y*y);
           v = [random() * d *  y,
                random() * d * -x,
                random() * 0.2 - 0.1];
       } else {
           v = [random()-0.5, random()-0.5, random()-0.5];
       }

       var m = initialMassRange[0] + random() * massRange;
       if (m > maxMass) { maxMass = m; }
       totalMass += m;
       massHistogram[binMass(m)] += 1;

       particles.push(new Particle(x, y, z, v, m));
   }

   return particles;

```

};

var particles = initialiseParticles(numParticles);

// Adjust to keep centred on the centre of mass
// But maybe this never changes
var centreParticles = function(particles) {

```
   var mid = [0, 0, 0];
   var i;

   for (var d=0; d<3; d++) {
       for (i=0; i<particles.length; i++) {
           var p = particles[i];
           if (p !== undefined) {
               mid[d] += p.mass * p.position[d];
           }
       }

       mid[d] /= totalMass;

       for (i=0; i<particles.length; i++) {
           particles[i].position[d] -= mid[d];
       }
   }

```

```
   resetMatrix();
   translate(centreX, centreY);
   scale(1/currentScale, 1/currentScale);

```

};

var drawSlider = function(x, y) {

```
   strokeWeight(1);
   fill(20, 20, 50);

   textAlign(LEFT, CENTER);
   textFont(myFont, 14);
   textSize(12);
   text("Scale 1:" + round(currentScale * 10)/10, x, y-16);

   rect(x, y, sliderWidth, 3, 4);
   stroke(160, 160, 160);
   line(x + 1, y, x + sliderWidth - 2, y);

```

```
   fill(180, 180, 180);
   stroke(50, 50, 50);
   var proportion = (currentScale - 0.5) / (maxScale - 0.5);
   var buttonX = x + sliderWidth * proportion - 5;

   rect(buttonX, y-7, 10, 16, 3);
   line(buttonX + 3, y - 2, buttonX + 7, y - 2);
   line(buttonX + 3, y + 1, buttonX + 7, y + 1);
   line(buttonX + 3, y + 4, buttonX + 7, y + 4);

```

};

var drawHistogram = function() {

```
   var barWidth = 9;
   var x = 265;
   var y = 400 - 18;
   var i;

   // Axis
   stroke(10,10,10);
   var axisLength = massHistogram.length * (barWidth+1) + 2;
   line(x, y, x + axisLength, y);

   // Label
   fill(10, 10, 20);
   textFont(myFont, 11);
   textAlign(CENTER, BASELINE);
   text("log(mass) distribution", x + axisLength/2, y+12);

   // Bars
   noStroke();

   var maxHeight = 5;
   for(i=0; i<massHistogram.length; i++) {
       if (massHistogram[i] > maxHeight) {
           maxHeight = massHistogram[i];
       }
   }

   var scaleHeight = (toolbarHeight - 35) / maxHeight;

   textFont(myFont, 10);
   for(i=0; i<massHistogram.length; i++) {
       var barHeight = round(massHistogram[i] * scaleHeight);
       fill(250, 250, 255, 180);
       rect(x+1, y-barHeight, barWidth, barHeight);
       fill(10, 10, 20);
       text(massHistogram[i], x+barWidth/2, y-barHeight-2);
       x += barWidth + 1;
   }

```

};

var findMeanAngularMomentum = function() {

```
   var angularM = [0,0,0];
   for (var p=0; p<particles.length; p++) {
       var a = particles[p].position;
       var b = particles[p].velocity;
       var x = a[1] * b[2] - a[2] * b[1];
       var y = a[2] * b[0] - a[0] * b[2];
       var z = a[0] * b[1] - a[1] * b[0];
       var d = sqrt(x*x + y*y + z*z) /
               (angularMomentumLineHeight * currentScale);

       // Normalise and scale by mass
       //var m = particles[p].mass / d;
       var m = 10 / d;
       angularM[0] += x * m;
       angularM[1] += y * m;
       angularM[2] += z * m;
   }
   angularM[0] /= totalMass;
   angularM[1] /= totalMass;
   angularM[2] /= totalMass;
   return angularM;

```

};

var drawAngularMomentum = function() {

```
   var am = findMeanAngularMomentum();
   stroke(255, 0, 0);
   strokeWeight(currentScale*2);
   line(-am[0], -am[1], am[0], am[1]);

```

};

var findMostDistanceParticle = function() {

```
   var maxDistance = 0;
   for (var i = 0; i < particles.length; i++) {
       var p = particles[i];
       var d = p.position[0] * p.position[0];
       d += p.position[1] * p.position[1];
       d += p.position[2] * p.position[2];
       d = sqrt(d);
       if (d > maxDistance) {
           maxDistance = d;
       }
   }
   return maxDistance;

```

};

var handleMouseEvents = function() {

```
   var y = 400 - toolbarHeight;
   mouseOverButton = false;

   if (mouseY > y) {
       resetMatrix();

       fill(200, 200, 200, 100);
       rect(0, y, 400, toolbarHeight);

       strokeWeight(2);
       stroke(200, 200, 200);
       line(0, y, 400, y);

       drawSlider(sliderX, sliderY);
       fill(20, 20, 50);
       textAlign(LEFT, CENTER);
       text("Number of bodies: " + particles.length,
            120, y+20);
       text("Most massive body: " + round(maxMass),
            120, y+40);
       text("Most distant body: "+round(findMostDistanceParticle()),
            120, y+60);

       drawHistogram();

       strokeWeight(1);
       fill(200, 200, 200, 100);

       // Mouseover effect
       if (mouseY > buttonY && mouseX > 60 && mouseX < 75) {
           fill(240, 240, 240);
           mouseOverButton = true;
       }

       if (running) {
           // Pause button
           rect(60, buttonY, 5, 20, 5);
           rect(69, buttonY, 5, 20, 5);
       } else {
           triangle(60.5, buttonY,
                    74, buttonY + 8,
                    60.5, buttonY + 16);
       }
   }

```

};

var rotateY3D = function(theta){

```
   var ct = cos(theta);
   var st = sin(theta);
   var x, z;

   for (var i = 0; i < particles.length; i+=1) {
       var p = particles[i];
       x = p.position[0];
       z = p.position[2];
       p.position = [ct * x + st * z,
                     p.position[1],
                     -st * x + ct * z];

       x = p.velocity[0];
       z = p.velocity[2];
       p.velocity = [ct * x + st * z,
                     p.velocity[1],
                     -st * x + ct * z];
   }

```

};

var rotateX3D = function(theta){

```
   var ct = cos(theta);
   var st = sin(theta);
   var y, z;

   for (var i = 0; i < particles.length; i++) {
       var p = particles[i];
       y = p.position[1];
       z = p.position[2];
       p.position = [p.position[0],
                     ct*y - st*z,
                     st*y + ct*z];
       y = p.velocity[1];
       z = p.velocity[2];
       p.velocity = [p.velocity[0],
                     ct*y - st*z,
                     st*y + ct*z];
   }

```

};

var sortByZ = function(a, b) {

```
   return a.position[2] - b.position[2];

```

};

var draw = function() {

```
   var i, j;

   if (running) {
       // Gravitational attraction
       for (i=0; i<particles.length; i++) {
           for (j=i+1; j<particles.length; j++) {
               particles[i].attract(particles[j]);
           }
       }

       // Combining particles
       var particles_to_remove = [];
       for (i=0; i<particles.length; i++) {
           for (j=0; j<particles[i].combineWith.length; j++) {
               var p = particles[i].combineWith[j];
               particles[i].mergeWith(p);
               particles_to_remove.push(particles.indexOf(p));
           }
       }

       // Remove particles
       particles_to_remove.sort();
       for (i=0; i<particles_to_remove.length; i++) {
           var r = particles_to_remove[i];
           var particle = particles[r];
           if (particle !== undefined) {
               massHistogram[binMass(particle.mass)]--;
               particles.splice(r, 1);
           }
       }
       particles_to_remove = [];

       // Moving
       for (i=0; i<particles.length; i++) {
           particles[i].move();
       }
       //particles.sort(sortByZ);
   }

   centreParticles(particles);
   background(backgroundColour);

   drawAngularMomentum();

   noStroke();
   for (i=0; i<particles.length; i++) {
       particles[i].draw();
   }

   handleMouseEvents();

```

};

var mouseClicked = function() {

```
   if (mouseOverButton) {
       if (running) { running = false; }
       else { running = true; }
   }

```

};

var mouseDragged = function() {

```
   if (mouseY < 400 - toolbarHeight) {
       // Rotate universe
       rotateY3D(-(pmouseX - mouseX) * rotateRate);
       rotateX3D( (pmouseY - mouseY) * rotateRate);
   } else if (mouseX > sliderX &&
              mouseX < sliderX + sliderWidth &&
              mouseY > sliderY - 7 &&
              mouseY < sliderY + 9) {
       // Drag slider for scale
           var proportion = (mouseX - sliderX)/sliderWidth;
           currentScale = 0.3+proportion*(maxScale - 0.3);
           colourScale = initialMaxDistance * currentScale;
   }

```

};

var mouseOut = function(){

```
   mouseIsPressed = false;

```

};

var keyPressed = function() {

```
   switch (keyCode) {
       case 37:
           centreX += 20;
           break;
       case 38:
           centreY += 20;
           break;
        case 39:
           centreX -= 20;
           break;
       case 40:
           centreY -= 20;
           break;
   }

```

};

rotateX3D(90);