I have to create some falling snowflakes in javascript, but they have change current path if they get collision with other flake. Something like on this image:

Here is my current code: http://codepen.io/wojtek1150/pen/QyaYdY
var flakePositions = [[]];
var temp = 0;
// snowflake proto
function Snowflake() {
this.pos = new Physics();
// snowflake guid
this.id = '';
// inits
this.MAX_X_START_POS = 100;
this.X_START_OFFSET = 0;
this.MAX_Y_START_POS = 50;
this.Y_START_OFFSET = -50;
this.MAX_X_SPEED = 4;
this.MAX_Y_SPEED = 1.2;
// use to get sin && cos
this.animationStepsCounter = 0
this.fallFactor = 100;
// snowflake html
this.getId = function () {
if (this.id == '') {
this.id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
function (c) {
var r = crypto.getRandomValues(new Uint8Array(1))[0] % 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
return this.id;
}
this.initalize = function () {
temp++;
//var size = 5 + Math.random() * 20;
var size = 20;
this.flakeDOM.style.width = size + "px";
this.flakeDOM.style.height = size + "px";
this.flakeDOM.style.opacity = Math.random();
this.pos.x = (Math.random() * this.MAX_X_START_POS);
this.pos.y = this.Y_START_OFFSET+(Math.random() * this.MAX_Y_START_POS);
this.pos.xSpeed = Math.random() * this.MAX_X_SPEED* Math.sign(-0.5 + Math.random());
this.pos.ySpeed = Math.random() * this.MAX_Y_SPEED;
//create array
flakePositions[temp] = [];
flakePositions[temp]['id'] = this.id;
flakePositions[temp]['x'] = this.flakeDOM.style.top;
flakePositions[temp]['y'] = this.flakeDOM.style.left;
flakePositions[temp]['width'] = this.flakeDOM.style.width;
flakePositions[temp]['height'] = this.flakeDOM.style.height;
flakePositions[temp]['xspeed'] = this.pos.xSpeed;
flakePositions[temp]['yspeed'] = this.pos.ySpeed
}
this.move = function () {
this.flakeDOM.style.top = (this.pos.y+=this.pos.ySpeed) + "px";
this.flakeDOM.style.left = (this.pos.x += Math.sin(this.animationStepsCounter/this.fallFactor) * this.pos.xSpeed) + "px";
this.animationStepsCounter += this.pos.ySpeed;
//update array
flakePositions[temp]['x'] = this.flakeDOM.style.top;
flakePositions[temp]['y'] = this.flakeDOM.style.left;
//check position with rest
for (var i = 1, len = flakePositions.length; i < len-1; i++) {
var rect1 = flakePositions[i];
var rect1d = rect1['id'];
var rect1sx = rect1['xspeed'];
var rect1sy = rect1['yspeed'];
var rect1x = parseInt(rect1['x']);
var rect1y = parseInt(rect1['y']);
for (var j = 2, len = flakePositions.length; j < len; j++) {
var rect2 = flakePositions[j];
var rect2d = rect2['id'];
var rect2x = parseInt(rect2['x']);
var rect2y = parseInt(rect2['y']);
//if(rect1x == rect2x && rect1y == rect2y)
if(rect1x < rect2x + 10 && rect1x + 10 > rect2x &&
rect1y < rect2y + 10 && 10 + rect1y > rect2y )
{
console.log('collision detected');
var t = document.getElementById(rect1d);
t.style.top = t.style.top+rect1sx+10;
t.style.left = t.style.left+rect1sy-10;
}
}
}
}
}
function Physics() {
// pos
this.x = 0;
this.y = 0;
this.z = 0;
// speed
this.xSpeed = 0;
this.ySpeed = 0;
this.zSpeed = 0;
// acceleration
this.xAccel = 1;
this.yAccel = 1;
this.zAccel = 1;
}
snowflakes = new Array();
var interval = 0;
function makeThisBoom() {
// snowflakes container
snowfield = document.getElementById('snow');
// snowflakes count
snoflakesCount = 20;
for (var i = 0; i < snoflakesCount; i++) {
snowflakes[i] = new Snowflake();
var flake = document.createElement('div');
snowflakes[i].flakeDOM = flake;
flake.id = snowflakes[i].getId();
flake.classList.add('sf');
snow.appendChild(flake);
snowflakes[i].initalize();
snowflakes[i].move();
}
interval = setInterval(anime,50);
}
function anime() {
for (var flake of snowflakes) {
flake.move();
}
}
function setInterface() {
document.getElementById('startstop').onclick = function () {
if (interval != 0) {
clearInterval(interval);
interval = 0;
} else interval = setInterval(anime, 50);
}
}
document.addEventListener("DOMContentLoaded", makeThisBoom);
document.addEventListener("DOMContentLoaded", setInterface);
.sf{
position:absolute;
z-index:9999999;
/*background: -moz-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
background: -webkit-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%);
background: radial-gradient(ellipse at center, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#00ffffff',GradientType=1 );
*/
border-radius:50%;
display:block;
width:20px; height:20px;
/* FOR DEV ONLY */
background:#FFF;
opacity:1!important;
}
body{
background:#222;
overflow:hidden;
}
#snow {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
#startstop{
width:100px;
height:30px;
border:0;
background:rgb(61, 95, 123);
color:#FFF;
outline:none;
}
<button id="startstop">Start/stop</button>
<div id="snow"> </div>
I already know how to get positions and I got if statement to check if there is any collision. But I don't know how to change path in proper way, even just bounce it :(
Any suggestions?
One of the key things for you to think about when doing this, are the cost/reward of implementing this kind of feature. I don't think these collisions will help you create the illusion of snowfalling. When the snowflakes in your current iteration miss each other it gives the illusion of 3d. If they were to hit each other and bounce it might give the incorrect illusion of balls falling in a 2d plane.
That being said, to implement what you are asking without using a library would be a huge time sync. I would recommend taking a look at PhysicsJS or matter-js.
Below you can see the function I personally use in a library I was working on. You can adapt most of it for your use. The truth is this a complicated ask.
define( 'detect/detectCircleCircleCollision' , [ 'lib/underscore' ] , function ( _ ) {
return function detectCircleCircleCollision ( circlePositionA,
circleRadiusA,
circleDisplacementA,
circlePositionB,
circleRadiusB,
circleDisplacementB,
boolean,
normalBody ) {
boolean = _.isUndefined( boolean ) || boolean ? true : false;
normalBody = _.isUndefined( normalBody ) || normalBody ? true : false;
var
relativePosition = circlePositionA.subtract.new( circlePositionB ),
combineRadius = circleRadiusA + circleRadiusB,
relativePositionDotProduct = relativePosition.lengthSqr(),
relativeDisplacement = circleDisplacementA.subtract.new( circleDisplacementB ),
a,b,c,r,t,newCircleOnePosition,newCircleTwoPosition,newCirclePositionDifference,collisionPoint;
if ( relativePositionDotProduct < combineRadius * combineRadius ) {
if ( boolean ) return true;
return collision( 0,//Time
circlePositionB.add.new( vector( relativePosition ).magnitude.set( circleRadiusA ) ),//point
relativePosition.normalize(),//Normal
normalBody,//normalbody
vector( relativePosition ).magnitude.set( circleRadiusA + circleRadiusB - relativePosition.magnitude() ) );//intersection
}
a = relativeDisplacement.dotProduct( relativeDisplacement );
b = relativePosition.dotProduct( relativePosition );
c = relativePositionDotProduct - combineRadius * combineRadius;
r = b * b - a * c;
if ( r < 0 ) return false;
t = -b - r * r / a;
if ( t > 1 || t < 0 ) return false;
else if ( boolean ) return true;
newCircleOnePosition = circleDisplacementA.scale.new( t ).add( circlePositionA );
newCircleTwoPosition = circleDisplacementB.scale.new( t ).add( circlePositionB );
newCirclePositionDifference = newCircleTwoPosition.subtract.new( newCircleOnePosition ).normalize();
collisionPoint = newCirclePositionDifference.scale.new( circleRadiusA );
return collision( t,
collisionPoint.add( newCircleOnePosition ),
newCirclePositionDifference,
normalBody,
false );
};
} );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With