Crazy Water Like Physic Stuff....

HTML
<canvas></canvas>
CSS
html, body { position: absolute; overflow: hidden; margin: 0; padding: 0; width: 100%; height: 100%; background:#cecece; touch-action: none; content-zooming: none; } canvas { position: absolute; width: 100%; height: 100%; cursor: crosshair; }
JAVASCRIPT
"use strict"; { class Grid { constructor(maxParticlesPerCell) { this.max = maxParticlesPerCell; } initSize(width, height, size) { this.width = (2 + width / size) | 0; this.height = (2 + height / size) | 0; this.size = size; this.cells = new Array(this.width * this.height * this.max); this.cellsSize = new Uint8Array(this.width * this.height); } fill(particles) { for (let p of particles) { const index = ((1 + p.y / this.size) | 0) * this.width + ((1 + p.x / this.size) | 0); if (this.cellsSize[index] < this.max) { const cellPos = this.cellsSize[index]++; this.cells[index * this.max + cellPos] = p; } } } update(particles) { for (let i = 0; i < this.width * this.height; ++i) { for (let j = 0; j < this.cellsSize[i]; ++j) { const p = this.cells[i * this.max + j]; const index = ((1 + p.y / this.size) | 0) * this.width + ((1 + p.x / this.size) | 0); if (index !== i && this.cellsSize[index] < this.max) { this.cells[index * this.max + this.cellsSize[index]++] = p; this.cells[i * this.max + j] = this.cells[ i * this.max + --this.cellsSize[i] ]; } } } } } class Contact { constructor(n, q, vx, vy) { this.n = n; this.q = q; this.vx = vx; this.vy = vy; } } class Particle { constructor(x, y) { this.x = x; this.y = y; this.px = x; this.py = y; } turbine() { const dx = pointer.x - this.x; const dy = pointer.y - this.y; const d = Math.sqrt(dx * dx + dy * dy); if (d < 2 * kRadius) { const angle = Math.atan2(dy, dx) + kRadius / (d + 1); this.x += Math.cos(angle); this.y += Math.sin(angle); } } integrate() { sun.collide(this); container.limit(this); if (pointer.isDown && !sun.drag) this.turbine(); const x = this.x; const y = this.y; this.x += x - this.px; this.y += y - this.py + kGravity; this.px = x; this.py = y; } fluid() { // Ref Grant Kot Material Point Method http://grantkot.com/ let pressure = 0; let presnear = 0; const neighbors = []; const xc = (1 + this.x / grid.size) | 0; const yc = (1 + this.y / grid.size) | 0; for (let x = xc - 1; x < xc + 2; ++x) { for (let y = yc - 1; y < yc + 2; ++y) { const index = y * grid.width + x; for ( let k = grid.max * index, end = k + grid.cellsSize[index]; k < end; ++k ) { const pn = grid.cells[k]; if (pn !== this) { const vx = pn.x - this.x; const vy = pn.y - this.y; const slen = vx * vx + vy * vy; if (slen < kRadius * kRadius) { const vlen = slen ** 0.5; const q = 1.0 - vlen / kRadius; pressure += q * q; presnear += q * q * q; neighbors.push(new Contact(pn, q, vx / vlen * q, vy / vlen * q)); } } } } } pressure = (pressure - kDensity) * 1.0; presnear *= 0.5; for (let p of neighbors) { const pr = pressure + presnear * p.q; const dx = p.vx * pr; const dy = p.vy * pr; p.n.x += dx; p.n.y += dy; this.x -= dx; this.y -= dy; if (p.q > kRendering) { ctx.moveTo(this.x, this.y); ctx.lineTo(p.n.x, p.n.y); } } } } class Circle { constructor(x, y, r) { this.x = x; this.y = y; this.px = x; this.py = y; this.dx = 0; this.dy = 0; this.r = r; this.drag = false; this.over = false; } anim() { const dx = pointer.x - this.x; const dy = pointer.y - this.y; if (Math.sqrt(dx * dx + dy * dy) < this.r) { if (!this.over) { this.over = true; canvas.elem.style.cursor = "pointer"; } } else { if (this.over && !this.drag) { this.over = false; canvas.elem.style.cursor = "crosshair"; } } if (this.drag) { this.x = pointer.ex + this.dx; this.y = pointer.ey + this.dy; } container.limit(this, this.r); const x = this.x; const y = this.y; this.x += this.x - this.px; this.y += this.y - this.py + 2 * kGravity; this.px = x; this.py = y; ctx.beginPath(); ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI); ctx.fillStyle = "#334"; ctx.fill(); } collide(p) { const dx = p.x - this.x; const dy = p.y - this.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < this.r * 1.2) { const fx = dx / dist; const fy = dy / dist; p.x += fx; p.y += fy; this.x -= 0.01 * fx; this.y -= 0.01 * (fy + 2 * Math.abs(fy)); } } } const container = { init(scale) { this.ai = 0; this.scale = scale; this.borders = [ new this.Plane(), new this.Plane(), new this.Plane(), new this.Plane() ]; }, Plane: class { constructor() { this.x = 0; this.y = 0; this.d = 0; } distanceToPlane(p) { return ( (p.x - canvas.width * 0.5) * this.x + (p.y - canvas.height * 0.5) * this.y + this.d ); } update(x, y, d) { this.x = x; this.y = y; this.d = d; } }, rotate() { const w = canvas.width; const h = canvas.height; const s = this.scale; const angle = Math.sin((this.ai += pointer.isDown ? 0 : 0.05)) * s * Math.min(1.0, h / w); const cos = Math.cos(angle); const sin = Math.sin(angle); this.borders[0].update(-sin, cos, -h * s); this.borders[1].update(cos, sin, -w * s); this.borders[2].update(-cos, -sin, -w * s); this.borders[3].update(sin, -cos, -h * s); ctx.save(); ctx.fillStyle = "#fff"; ctx.translate(w * 0.5, h * 0.5); ctx.rotate(angle); ctx.fillRect(-w * s, -h * s, w * s * 2, h * s * 2); ctx.restore(); }, limit(p, radius = 0) { for (let b of this.borders) { let d = b.distanceToPlane(p) + radius + 0; if (d > 0) { p.x += b.x * -d + (Math.random() * 0.1 - 0.05); p.y += b.y * -d + (Math.random() * 0.1 - 0.05); } } } }; const canvas = { init() { this.elem = document.querySelector('canvas'); this.resize(); window.addEventListener("resize", () => canvas.resize(), false); return this.elem.getContext("2d", { alpha: false }); }, resize() { this.width = this.elem.width = this.elem.offsetWidth; this.height = this.elem.height = this.elem.offsetHeight; kRadius = Math.round(0.04 * Math.sqrt(this.width * this.height)); grid.initSize(this.width, this.height, kRadius); grid.fill(particles); if (sun) sun.r = 1.5 * kRadius; } }; const pointer = { init(canvas) { this.x = this.ex = 0; this.y = this.ey = 2000; this.isDown = false; window.addEventListener("mousemove", e => this.move(e, false), false); canvas.elem.addEventListener("touchmove", e => this.move(e, true), false); window.addEventListener("mousedown", e => this.down(e, false), false); window.addEventListener("touchstart", e => this.down(e, true), false); window.addEventListener("mouseup", e => this.up(e, false), false); window.addEventListener("touchend", e => this.up(e, true), false); }, move(e, touch) { if (touch) { e.preventDefault(); this.x = e.targetTouches[0].clientX; this.y = e.targetTouches[0].clientY; } else { this.x = e.clientX; this.y = e.clientY; } }, down(e, touch) { this.isDown = true; this.move(e, touch); if (touch) sun.anim(); if (sun.over) { sun.drag = true; this.ex = this.x; this.ey = this.y; sun.dx = sun.x - this.ex; sun.dy = sun.y - this.ey; if (!touch) canvas.elem.style.cursor = "move"; } }, up(e, touch) { this.isDown = false; if (!touch) canvas.elem.style.cursor = "crosshair"; sun.drag = false; sun.over = false; }, ease(n) { this.ex += (this.x - this.ex) * n; this.ey += (this.y - this.ey) * n; } }; const initParticles = num => { const s = container.scale; let x = canvas.width * s * 0.5; let y = canvas.height * s * 0.5; for (let i = 0; i < num; ++i) { particles.push(new Particle(x, y)); x += kRadius / 2.5; if (x > canvas.width * (1 - s * 0.5)) { x = canvas.width * s * 0.5; y += kRadius / 3; } } grid.fill(particles); } let sun; let kRadius; const particles = []; const grid = new Grid(100); const ctx = canvas.init(); pointer.init(canvas); container.init(0.35); const kGravity = 0.04; const kDensity = 3; const kRendering = 0.45; initParticles(1200); sun = new Circle( canvas.width * 0.5, canvas.height * 0.5 - kRadius, 1.5 * kRadius ); const run = () => { requestAnimationFrame(run); ctx.fillStyle = "#bebebf"; ctx.fillRect(0, 0, canvas.width, canvas.height); pointer.ease(0.25); container.rotate(); for (let p of particles) p.integrate(); grid.update(particles); ctx.beginPath(); ctx.strokeStyle = "#556"; for (let p of particles) p.fluid(); ctx.stroke(); sun.anim(); }; run(); }
Expand for more options Login