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, #particle2 { |
| 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 multi-fluids Simulation ***** |
| // ----------------------------------------------------------------------------- |
| // JavaScript: Gerard Ferrandez - 14 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, |
| particles = [], grid = [], neighbors = [], mousePressed, |
| mouseX = 0, mouseY = 0, fps = 0, nParticles = 0, gridResolution, rad; |
| // ----- Particle prototype ----- |
| var Particle = function (type, img, x, y) { |
| this.type = type; |
| this.particleImage = img; |
| this.width = img.width * 0.5; |
| this.height = img.height * 0.5; |
| 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 < this.width) this.x = this.width; else if (this.x > nw - this.width) this.x = nw - this.width; |
| if (this.y < this.height) this.y = this.height; else if (this.y > nh - this.height) this.y = nh - this.height; |
| // ----- 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 = presnear * np.q + pressure + (this.type != np.type) * 0.35; |
| var dx = np.vxl * p; |
| var dy = np.vyl * p; |
| 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(this.particleImage, this.x - this.width, this.y - this.height); |
| } |
| // ----- 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 ----- |
| gridResolution = params.gridResolution; |
| rad = params.rad; |
| // ----- DOM elements ----- |
| var d = canvas = document.getElementById("screen"); |
| ctx = canvas.getContext("2d"); |
| // ---- 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 k = 0; k < params.numParticles.length; k++) { |
| for (var i = 0; i < params.numParticles[k]; i++ ) { |
| particles[nParticles++] = new Particle( |
| k, |
| document.getElementById(params.imgParticles[k]), |
| 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 ----- |
| numParticles: [500, 500], |
| imgParticles: ["particle", "particle2"], |
| gridResolution: 20, |
| rad: 22 |
| }); |
| </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"> |
| <img id = "particle2" src="../images/bouton_orange.png"> |
| </body> |
| </html> |


Recent Comments
April 12, 2012 (6:14)
January 13, 2012 (7:45)
January 5, 2012 (2:35)