Direct Link
Source code
| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Fluid dynamics - Interactive DHTML demos</title> |
| <meta name="Author" content="Gerard Ferrandez at http://www.dhteumeuleu.com"> |
| <style type="text/css"> |
| body { |
| margin: 0px; |
| padding: 0px; |
| background: #111; |
| width: 100%; |
| height: 100%; |
| color:#fff; |
| font-size:12px; |
| font-family: arial,verdana; |
| } |
| #screen { |
| position: absolute; |
| left: 50%; |
| top: 50%; |
| background: #000; |
| overflow: hidden; |
| width: 400px; |
| height: 400px; |
| margin-left:-200px; |
| margin-top:-200px; |
| outline: #222 solid 1px; |
| } |
| #screen img { |
| position: absolute; |
| } |
| #particle { |
| visibility: hidden; |
| } |
| #fps { |
| position: absolute; |
| left: 50%; |
| top: 50%; |
| width: 400px; |
| margin-top: 205px; |
| margin-left:-200px; |
| text-align: right; |
| color:#666; |
| } |
| </style> |
| <script type="text/javascript"> |
| // =============================================================================== |
| // ***** Particle-based JS Fluid Simulation ***** |
| // ----------------------------------------------------------------------------- |
| // JavaScript: Gerard Ferrandez - 8 May 2011 |
| // http://www.dhteumeuleu.com/ |
| // use under a CC-BY-NC license |
| // ----------------------------------------------------------------------------- |
| // Freely adapted from Grant Kot visco-elastic Java implementation |
| // http://kotsoft.googlepages.com/Viscoelastic2.html |
| // Research paper - |
| // http://www.iro.umontreal.ca/labs/infographie/papers/Clavet-2005-PVFS/pvfs.pdf |
| // =============================================================================== |
| "use strict"; |
| (function () { |
| // ----- private vars ----- |
| var canvas, ctx, nw, nh, nx, ny, nbX, nbY, particleImage, particleWidth, particleHeight, |
| particles = [], grid = [], neighbors = [], mousePressed, |
| mouseX = 0, mouseY = 0, fps = 0, nParticles, gridResolution, rad; |
| // ----- Particle prototype ----- |
| var Particle = function (x, y) { |
| this.x = x; |
| this.y = y; |
| this.pprevx = x; |
| this.pprevy = y; |
| this.velx = 0; |
| this.vely = 0; |
| }; |
| Particle.prototype.pass1 = function () { |
| // ----- maintain spatial hashing grid ------ |
| var g = grid[Math.round(this.y / gridResolution) * nbX + Math.round(this.x / gridResolution)]; |
| g.neighborsParticles[g.len++] = this; |
| // ----- mouse pressed ----- |
| if (mousePressed) { |
| var vx = this.x - mouseX; |
| var vy = this.y - mouseY; |
| var vlen = Math.sqrt(vx * vx + vy * vy); |
| if (vlen >= 1 && vlen < 60) { |
| this.velx += 0.5 * rad * (vx / vlen) / vlen; |
| this.vely += 0.5 * rad * (vy / vlen) / vlen; |
| } |
| } |
| // apply gravity |
| this.vely += 0.01; |
| // save previous position |
| this.pprevx = this.x; |
| this.pprevy = this.y; |
| // advance to predicted position |
| this.x += this.velx; |
| this.y += this.vely; |
| } |
| // ----- Double Density Relaxation Algorithm ----- |
| Particle.prototype.pass2 = function () { |
| var pressure = 0, presnear = 0, nl = 0; |
| // ----- get grid position ----- |
| var xc = Math.round(this.x / gridResolution); |
| var yc = Math.round(this.y / gridResolution); |
| // ----- 3 x 3 grid cells ----- |
| for (var xd = -1; xd < 2; xd++){ |
| for (var yd = -1; yd < 2; yd++){ |
| var h = grid[(yc + yd) * nbX + (xc + xd)]; |
| if (h && h.len) { |
| // ----- foreach neighbors pair ----- |
| for (var a = 0, l = h.len; a < l; a++) { |
| var pn = h.neighborsParticles[a]; |
| if (pn != this) { |
| var vx = pn.x - this.x; |
| var vy = pn.y - this.y; |
| var vlen = Math.sqrt(vx * vx + vy * vy); |
| if (vlen < rad){ |
| // ----- compute density and near-density ----- |
| var q = 1 - (vlen / rad); |
| pressure += q * q; // quadratic spike |
| presnear += q * q * q; // cubic spike |
| pn.q = q; |
| pn.vxl = (vx / vlen) * q; |
| pn.vyl = (vy / vlen) * q; |
| neighbors[nl++] = pn; |
| } |
| } |
| } |
| } |
| } |
| } |
| // ----- screen limits ----- |
| if (this.x < rad){ |
| var q = 1 - Math.abs(this.x / rad); |
| this.x += q * q * 0.5; |
| } else if (this.x > nw - rad){ |
| var q = 1 - Math.abs((nw - this.x) / rad); |
| this.x -= q * q * 0.5; |
| } |
| if (this.y < rad){ |
| var q = 1 - Math.abs(this.y / rad); |
| this.y += q * q * 0.5; |
| } else if (this.y > nh - rad){ |
| var q = 1 - Math.abs((nh - this.y) / rad); |
| this.y -= q * q * 0.5; |
| } |
| if (this.x < particleWidth) this.x = particleWidth; else if (this.x > nw - particleWidth) this.x = nw - particleWidth; |
| if (this.y < particleHeight) this.y = particleHeight; else if (this.y > nh - particleHeight) this.y = nh - particleHeight; |
| // ----- second pass of the relaxation ----- |
| pressure = (pressure - 3) * 0.5; |
| presnear *= 0.5; |
| for (var a = 0; a < nl; a++){ |
| var np = neighbors[a]; |
| // apply displacements |
| var p = pressure + presnear * np.q; |
| var dx = (np.vxl * p) * 0.5; |
| var dy = (np.vyl * p) * 0.5; |
| np.x += dx; |
| np.y += dy; |
| this.x -= dx; |
| this.y -= dy; |
| } |
| } |
| Particle.prototype.pass3 = function () { |
| // use previous position to compute next velocity |
| this.velx = this.x - this.pprevx; |
| this.vely = this.y - this.pprevy; |
| // draw particles - HTML5 canvas drawImage |
| ctx.drawImage(particleImage, this.x - particleWidth, this.y - particleHeight); |
| } |
| // ----- main loop ----- |
| var run = function () { |
| ctx.clearRect(0, 0, nw, nh); |
| // ----- reset grid ----- |
| for (var i = 0, l = nbX * nbY; i < l; i++) grid[i].len = 0; |
| // ----- simulation passes ----- |
| for (var i = 0; i < nParticles; i++) particles[i].pass1(); |
| for (var i = 0; i < nParticles; i++) particles[i].pass2(); |
| for (var i = 0; i < nParticles; i++) particles[i].pass3(); |
| fps++; |
| }; |
| //////////////////////////////////////////////////////////////////////////////////////// |
| // ----- initialization ----- |
| var init = function (params) { |
| // ----- entry parameters ----- |
| nParticles = params.nParticles; |
| gridResolution = params.gridResolution; |
| rad = params.rad; |
| // ----- DOM elements ----- |
| var d = canvas = document.getElementById("screen"); |
| ctx = canvas.getContext("2d"); |
| particleImage = document.getElementById("particle"); |
| particleWidth = particleImage.width * 0.5; |
| particleHeight = particleImage.height * 0.5; |
| // ---- canvas dimensions ---- |
| nw = d.offsetWidth; |
| nh = d.offsetHeight; |
| canvas.width = nw; |
| canvas.height = nh; |
| for (nx = 0, ny = 0; d != null; d = d.offsetParent) { |
| nx += d.offsetLeft; |
| ny += d.offsetTop; |
| } |
| nbX = Math.round(nw / gridResolution) + 1; |
| nbY = Math.round(nh / gridResolution) + 1; |
| // ----- init grid (static for better js performance) ----- |
| for (var i = 0; i < nbX * nbY; i++) { |
| grid[i] = { |
| len: 0, |
| neighborsParticles: [] |
| } |
| }; |
| // ----- create particles ----- |
| for (var i = 0; i < nParticles; i++ ) { |
| particles[i] = new Particle(Math.random() * nw, Math.random() * nh); |
| } |
| // ----- mouse events ----- |
| document.onselectstart = function () { return false; } |
| document.ondrag = function () { return false; } |
| document.addEventListener('mousedown', function (e) { mousePressed = true; }, false); |
| document.addEventListener('mouseup', function (e) { mousePressed = false; }, false); |
| document.addEventListener('mousemove', function (e) { |
| mouseX = e.clientX - nx; |
| mouseY = e.clientY - ny; |
| }, false); |
| // ----- start engine ----- |
| setInterval(run, 16); |
| // ----- frames per second ----- |
| setInterval(function () { |
| document.getElementById('fps').innerHTML = fps + " fps"; |
| fps = 0; |
| }, 1000); |
| }; |
| return { |
| //////////////////////////////////////////////////////////////////////////// |
| // ---- launch script ----- |
| init : function (params) { |
| window.addEventListener('load', function () { |
| init(params); |
| }, false); |
| } |
| } |
| })().init({ |
| // ----- init parameters ----- |
| nParticles:1000, |
| gridResolution: 20, |
| rad: 20 |
| }); |
| </script> |
| </head> |
| <body> |
| <canvas id="screen">If the brute force approach doesn't work, you're obviously not using enough brute force!</canvas> |
| <div id="fps"></div> |
| <img id = "particle" src="../images/bouton_green.png"> |
| </body> |
| </html> |


Recent Comments
May 13, 2011 (3:16)
May 13, 2011 (1:51)
May 13, 2011 (3:20)