10
\$\begingroup\$

I have worked for a few hours and this is the result:

$(document).ready(function(){
  var canvas = $("#snow-collision")[0];
  var stats = new Stats();
  stats.setMode(1); // 0: fps, 1: ms
  var debug = $("#debug");
  var pi = Math.PI;
  var hpi = pi / 2;
  var tpi = hpi * 3;
  // align top-left
  stats.domElement.style.position = 'absolute';
  stats.domElement.style.left = '0px';
  stats.domElement.style.top = '0px';

  document.body.appendChild( stats.domElement );
  
  var ctx    = canvas.getContext("2d");
  var width  = canvas.width = $(window).width(),
      height  = canvas.height = $(window).height();
  
  var snow_canvas = $("#flake-creator")[0],
      snow_ctx = snow_canvas.getContext("2d");
  
  $(window).resize(function(){
    width  = canvas.width = $(window).width();
    height  = canvas.height = $(window).height();
  });
  
  var isClicking = false;
  $(document).on("mousedown mouseup", function(e){
    isClicking = e.type == "mousedown";
  });
  
  var xMouse = null,
      yMouse = null;
  
  $(document).mousemove(function(e){
    xMouse = e.pageX;
    yMouse = e.pageY;
  });
  
  var time = 0;
  var snowFlakes = [];
  var pi = Math.PI;
  var windForce = 1;
  
  setInterval(function(){
    stats.begin();
    time++;
    
    var lSnow = snowFlakes.length;
    if(lSnow < height) // I want the same num of snowflakes as the height of the screen!
    {
      var randomSize = RandomRange(5, 8);
      var xSnow      = RandomRange(randomSize, width - randomSize);
      var newSnow    = new Snowflake(xSnow, -randomSize, randomSize);
      var randomSnow = RandomSnowflake(randomSize * 2); 
      newSnow.ChangeImage(randomSnow);
      snowFlakes.push(newSnow);
    }
    // MOVE AROUND MOUSE
    //var md = Direction(xMouse, yMouse, width / 2, height / 2),
    //    dd = Distance(xMouse, yMouse, width / 2, height / 2) / 50;
    windForce = 0; //md > 90 && md < 270 ? dd : -dd;
    
    ctx.clearRect(0, 0, width, height);
    debug.text(lSnow + " snowflakes");
    while(lSnow--)
    {
      var snow  = snowFlakes[lSnow];
      
      var xSnow   = ~~snow.CordX,
          ySnow   = ~~snow.CordY,
          sSnow   = ~~snow.Size,
          dsSnow  = 2 * sSnow,
          rSnow   = 0;
      
      if(time % 1 === 0) // If it becomes laggy, to make the maths a bit slower
      {
        var d = Distance(xMouse, yMouse, xSnow, ySnow);
        var di = Direction(xMouse, yMouse, xSnow, ySnow);
        var force = d / 20;
        if(isClicking) force = -force;
        if(d < 100 && Math.round(snow.ForceX) == 0)
        {
          snow.ForceX = di > hpi && di < tpi ? force : -force;
        }
      }
      snow.Update();
      if(ySnow >= height)
      {
        snow.CordY = -sSnow;
        snow.CordX = RandomRange(sSnow, width - sSnow);
      }
      else if(xSnow > -dsSnow && xSnow < width + dsSnow && 
              ySnow > -dsSnow && ySnow < height + dsSnow)
      {
          ctx.save();
          ctx.translate(xSnow, ySnow);
          ctx.translate(sSnow, sSnow);
          ctx.rotate(rSnow);
          ctx.drawImage(snow.Image, -sSnow, -sSnow, dsSnow, dsSnow);
          ctx.restore();
      }
    }
    stats.end();
  }, 1000 / 60);
  
  function Snowflake(x, y, size)
  {
    this.Image   = null;
    this.Life    = 0;
    this.Size    = size;
    this.ChangeImage = function(i)
    { this.Image = i; };
    this.CordX    = x;
    this.CordY    = y;
    this.ForceX   = 0;
    
    this.Gravity  = RandomRange(3, 20);
    this.OnFloor = false;
    this.Update = function(force)
    {
      var life    = this.Life++;
      var xForce  = this.ForceX;
      
      if(xForce > 0) xForce = this.ForceX -= 0.1;
      else if(xForce < 0) xForce = this.ForceX += 0.1;

      this.CordX += xForce;
      this.CordY += Math.sqrt(this.Gravity) / 2;
    };
  }
  
  function RandomSnowflake(size)
  {
    // http://codepen.io/zny/pen/QwEvMR?editors=001 Modified
    snow_canvas.width = snow_canvas.height = size;
    //snow_ctx.clearRect(0, 0, size, size);
    var spark = 50;
    var randomArms = RandomRange(5, 8);
    var hs         = ~~(size / 2);
    var r          = size;
    var angle      = 2 * Math.PI / randomArms;
    var spShort    = RandomRange(0, 6),
        spN        = RandomRange(0, 4);
    
    var spY        = RandomRange(0, r), // y of first spark
        spYY       = RandomRange(0, r), // y of last spark
        spH        = RandomRange(0, spark), // height of sparks
        spDist     = spYY - spY;
    if (spDist > 0) spDist /= spN; 
    else 
    {
      spDist = (spY - spYY)/spN;
      spY = spYY;
      spYY = spY;
    }
    var r2      = Math.random() * r, // shorter than main
        spY2    = Math.random() * r2, // y of spark
        spW2    = Math.random() * spark, // width of spark
        spH2    = RandomRange(-spark, spark * 2), // height of spark
        spShort = RandomRange(0, 6); // 1 in 6 chance of shorts not showing
    
    snow_ctx.translate(hs, hs);
    var grad = snow_ctx.createRadialGradient(0, 0, 0, 0, 0, r);
    grad.addColorStop("0","rgba(255,255,255,0.7)");
    grad.addColorStop("0.4","rgba(255,255,255,0.85)");
    grad.addColorStop("0.8","rgba(255,255,255,0.2)");
    grad.addColorStop("1","rgba(255,255,255,0)");
    
    // Large arms
    snow_ctx.beginPath();
    for (var i = 0; i < randomArms; i++) 
    {
      snow_ctx.moveTo(0, 0);
      snow_ctx.lineTo(0, r);
      snow_ctx.rotate(angle);
    }
    snow_ctx.lineWidth = 1;
    snow_ctx.strokeStyle = grad;
    snow_ctx.stroke();
    snow_ctx.closePath();
    
    // Short arms
    
    if (spShort > 1) 
    {
      snow_ctx.beginPath();
      snow_ctx.rotate(angle / 2);
      for (var i = 0; i < randomArms; i++) 
      {
        snow_ctx.moveTo(0, 0);
        snow_ctx.lineTo(0, r2);
        snow_ctx.rotate(angle);
      }
      snow_ctx.lineWidth = 1;
      snow_ctx.strokeStyle = grad;
      snow_ctx.stroke();
      snow_ctx.closePath();

      //short spark
      snow_ctx.beginPath();
      snow_ctx.moveTo(0, 0);
      for (var i = 0; i < randomArms; i++) 
      {
        snow_ctx.moveTo(-spW2, spY2 + spH2);
        snow_ctx.lineTo(0, spY2);
        snow_ctx.lineTo(spW2, spY2 + spH2);
        snow_ctx.rotate(angle);
      }
      snow_ctx.lineWidth = 1;
      snow_ctx.strokeStyle = grad;
      snow_ctx.stroke();
      snow_ctx.closePath();
    }
    
    var image = new Image();
    image.src = snow_canvas.toDataURL("image/png");
    
    return image;
  }
  
  function RandomRange(min, max)
  { return Math.round(Math.random() * (max - min)) + min; }
  function Distance(x0, y0, x1, y1)
  {
    var xx = x1 - x0;
    var yy = y1 - y0;
    return Math.sqrt(xx * xx + yy * yy)
  }
  function Direction(x0, y0, x1, y1)
  {
    var xx = x1 - x0;
    var yy = y1 - y0;
    return pi + Math.atan2(yy, xx);
  }
});
body
{
  padding: 0px;
  margin: 0px;
  background-color: #0092B2;
}
#debug{ display: block; position: absolute; right: 0px; top: 0px; }
#snow-collision
{
  position: fixed;
  left: 0px;
  top: 0px;
  z-index: 0;
}
.hide{ display: none; }
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/stats.js/r11/Stats.min.js"></script>
<canvas id="snow-collision">
  Your browser doesnt support canvas-html5
</canvas>
<canvas id="flake-creator" class="hide">I can't create a snowflake :(</canvas>
<div id="debug"></div>

Unfortunately, when it has more than 1000 particles it becomes laggy. How can I improve it?

Preview

\$\endgroup\$
2
  • 1
    \$\begingroup\$ @MannyMeng Copying external code into the question violates Code Review policy. However, in this case, since the original author has edited the question after you inserted the code, I'll interpret that as implicit consent. \$\endgroup\$ Commented Dec 23, 2014 at 21:07
  • \$\begingroup\$ @200_success Sorry, I didn't know. \$\endgroup\$ Commented Dec 23, 2014 at 21:09

1 Answer 1

6
\$\begingroup\$

That is an awesome animation! From a technical viewpoint, I find it neat how you render the flakes into a PNG represented by a data URI.

Realism

A lot of the snowflakes look quite nice. But I see some square, pentagonal and heptagonal snowflakes! What kind of chemicals are you using? Is that even mathematically possible? Is some atmospheric condition causing water to form quasi-crystals?

The repulsive force pushes aside snowflakes that were originally within 100 pixels of the cursor. The flakes that are thus selected for repulsion continue to fly away with sideways momentum, even though other at that radius continue to fall unaffected. I would expect viscous forces to dominate over inertial forces (a "low Reynolds number" condition). (In programming terms, each flake should act as if it were "stateless", and shouldn't store xForce.)

Implementation

The vast majority of the flakes are falling straight down with constant vertical velocity. Since there are only 18 possible discrete values of Gravity, you could assign flakes to one of 18 divs, each div acting as a continuously scrolling layer. Then, within each layer, you only have to move the flakes that are close enough to the cursor to be affected by the simulated wind.

Functions should have lowerCase names by convention. Names such as Direction look like constructors.

The Direction() function would be more elegant without the pi +. Just use Math.atan() more strategically and write return Math.atan2(y0 - y1, x0 - x1);.

\$\endgroup\$
4
  • \$\begingroup\$ Thank, It has helped me a lot!, do you have some example how to use the reynolds number in my code? it could help me a lot! :D \$\endgroup\$ Commented Dec 23, 2014 at 22:56
  • \$\begingroup\$ I wouldn't use any equation that involves the Reynolds number directly. The point is, in that physical regime, you should model snow as if it had zero inertia. \$\endgroup\$ Commented Dec 23, 2014 at 23:00
  • \$\begingroup\$ I love it!, now It looks AWESOME! \$\endgroup\$ Commented Dec 23, 2014 at 23:59
  • \$\begingroup\$ and my code has been improved around 20%! \$\endgroup\$ Commented Dec 24, 2014 at 0:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.