diff --git a/src/gravity-points/app.js b/src/gravity-points/app.js new file mode 100644 index 0000000..8673ec2 --- /dev/null +++ b/src/gravity-points/app.js @@ -0,0 +1,532 @@ +/** + * requestAnimationFrame + */ +window.requestAnimationFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60); + }; +})(); + + +/** + * Vector + */ +function Vector(x, y) { + this.x = x || 0; + this.y = y || 0; +} + +Vector.add = function(a, b) { + return new Vector(a.x + b.x, a.y + b.y); +}; + +Vector.sub = function(a, b) { + return new Vector(a.x - b.x, a.y - b.y); +}; + +Vector.scale = function(v, s) { + return v.clone().scale(s); +}; + +Vector.random = function() { + return new Vector( + Math.random() * 2 - 1, + Math.random() * 2 - 1 + ); +}; + +Vector.prototype = { + set: function(x, y) { + if (typeof x === 'object') { + y = x.y; + x = x.x; + } + this.x = x || 0; + this.y = y || 0; + return this; + }, + + add: function(v) { + this.x += v.x; + this.y += v.y; + return this; + }, + + sub: function(v) { + this.x -= v.x; + this.y -= v.y; + return this; + }, + + scale: function(s) { + this.x *= s; + this.y *= s; + return this; + }, + + length: function() { + return Math.sqrt(this.x * this.x + this.y * this.y); + }, + + lengthSq: function() { + return this.x * this.x + this.y * this.y; + }, + + normalize: function() { + var m = Math.sqrt(this.x * this.x + this.y * this.y); + if (m) { + this.x /= m; + this.y /= m; + } + return this; + }, + + angle: function() { + return Math.atan2(this.y, this.x); + }, + + angleTo: function(v) { + var dx = v.x - this.x, + dy = v.y - this.y; + return Math.atan2(dy, dx); + }, + + distanceTo: function(v) { + var dx = v.x - this.x, + dy = v.y - this.y; + return Math.sqrt(dx * dx + dy * dy); + }, + + distanceToSq: function(v) { + var dx = v.x - this.x, + dy = v.y - this.y; + return dx * dx + dy * dy; + }, + + lerp: function(v, t) { + this.x += (v.x - this.x) * t; + this.y += (v.y - this.y) * t; + return this; + }, + + clone: function() { + return new Vector(this.x, this.y); + }, + + toString: function() { + return '(x:' + this.x + ', y:' + this.y + ')'; + } +}; + + +/** + * GravityPoint + */ +function GravityPoint(x, y, radius, targets) { + Vector.call(this, x, y); + this.radius = radius; + this.currentRadius = radius * 0.5; + + this._targets = { + particles: targets.particles || [], + gravities: targets.gravities || [] + }; + this._speed = new Vector(); +} + +GravityPoint.RADIUS_LIMIT = 65; +GravityPoint.interferenceToPoint = true; + +GravityPoint.prototype = (function(o) { + var s = new Vector(0, 0), p; + for (p in o) s[p] = o[p]; + return s; +})({ + gravity: 0.05, + isMouseOver: false, + dragging: false, + destroyed: false, + _easeRadius: 0, + _dragDistance: null, + _collapsing: false, + + hitTest: function(p) { + return this.distanceTo(p) < this.radius; + }, + + startDrag: function(dragStartPoint) { + this._dragDistance = Vector.sub(dragStartPoint, this); + this.dragging = true; + }, + + drag: function(dragToPoint) { + this.x = dragToPoint.x - this._dragDistance.x; + this.y = dragToPoint.y - this._dragDistance.y; + }, + + endDrag: function() { + this._dragDistance = null; + this.dragging = false; + }, + + addSpeed: function(d) { + this._speed = this._speed.add(d); + }, + + collapse: function(e) { + this.currentRadius *= 1.75; + this._collapsing = true; + }, + + render: function(ctx) { + if (this.destroyed) return; + + var particles = this._targets.particles, + i, len; + + for (i = 0, len = particles.length; i < len; i++) { + particles[i].addSpeed(Vector.sub(this, particles[i]).normalize().scale(this.gravity)); + } + + this._easeRadius = (this._easeRadius + (this.radius - this.currentRadius) * 0.07) * 0.95; + this.currentRadius += this._easeRadius; + if (this.currentRadius < 0) this.currentRadius = 0; + + if (this._collapsing) { + this.radius *= 0.75; + if (this.currentRadius < 1) this.destroyed = true; + this._draw(ctx); + return; + } + + var gravities = this._targets.gravities, + g, absorp, + area = this.radius * this.radius * Math.PI, garea; + + for (i = 0, len = gravities.length; i < len; i++) { + g = gravities[i]; + + if (g === this || g.destroyed) continue; + + if ( + (this.currentRadius >= g.radius || this.dragging) && + this.distanceTo(g) < (this.currentRadius + g.radius) * 0.85 + ) { + g.destroyed = true; + this.gravity += g.gravity; + + absorp = Vector.sub(g, this).scale(g.radius / this.radius * 0.5); + this.addSpeed(absorp); + + garea = g.radius * g.radius * Math.PI; + this.currentRadius = Math.sqrt((area + garea * 3) / Math.PI); + this.radius = Math.sqrt((area + garea) / Math.PI); + } + + g.addSpeed(Vector.sub(this, g).normalize().scale(this.gravity)); + } + + if (GravityPoint.interferenceToPoint && !this.dragging) + this.add(this._speed); + + this._speed = new Vector(); + + if (this.currentRadius > GravityPoint.RADIUS_LIMIT) this.collapse(); + + this._draw(ctx); + }, + + _draw: function(ctx) { + var grd, r; + + ctx.save(); + + grd = ctx.createRadialGradient(this.x, this.y, this.radius, this.x, this.y, this.radius * 5); + grd.addColorStop(0, 'rgba(0, 0, 0, 0.1)'); + grd.addColorStop(1, 'rgba(0, 0, 0, 0)'); + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius * 5, 0, Math.PI * 2, false); + ctx.fillStyle = grd; + ctx.fill(); + + r = Math.random() * this.currentRadius * 0.7 + this.currentRadius * 0.3; + grd = ctx.createRadialGradient(this.x, this.y, r, this.x, this.y, this.currentRadius); + grd.addColorStop(0, 'rgba(0, 0, 0, 1)'); + grd.addColorStop(1, Math.random() < 0.2 ? 'rgba(255, 196, 0, 0.15)' : 'rgba(103, 181, 191, 0.75)'); + ctx.beginPath(); + ctx.arc(this.x, this.y, this.currentRadius, 0, Math.PI * 2, false); + ctx.fillStyle = grd; + ctx.fill(); + ctx.restore(); + } +}); + + +/** + * Particle + */ +function Particle(x, y, radius) { + Vector.call(this, x, y); + this.radius = radius; + + this._latest = new Vector(); + this._speed = new Vector(); +} + +Particle.prototype = (function(o) { + var s = new Vector(0, 0), p; + for (p in o) s[p] = o[p]; + return s; +})({ + addSpeed: function(d) { + this._speed.add(d); + }, + + update: function() { + if (this._speed.length() > 12) this._speed.normalize().scale(12); + + this._latest.set(this); + this.add(this._speed); + } + + // render: function(ctx) { + // if (this._speed.length() > 12) this._speed.normalize().scale(12); + + // this._latest.set(this); + // this.add(this._speed); + + // ctx.save(); + // ctx.fillStyle = ctx.strokeStyle = '#fff'; + // ctx.lineCap = ctx.lineJoin = 'round'; + // ctx.lineWidth = this.radius * 2; + // ctx.beginPath(); + // ctx.moveTo(this.x, this.y); + // ctx.lineTo(this._latest.x, this._latest.y); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false); + // ctx.fill(); + // ctx.restore(); + // } +}); + + + +// Initialize + +(function() { + + // Configs + + var BACKGROUND_COLOR = 'rgba(11, 51, 56, 1)', + PARTICLE_RADIUS = 1, + G_POINT_RADIUS = 10, + G_POINT_RADIUS_LIMITS = 65; + + + // Vars + + var canvas, context, + bufferCvs, bufferCtx, + screenWidth, screenHeight, + mouse = new Vector(), + gravities = [], + particles = [], + grad, + gui, control; + + + // Event Listeners + + function resize(e) { + screenWidth = canvas.width = window.innerWidth; + screenHeight = canvas.height = window.innerHeight; + bufferCvs.width = screenWidth; + bufferCvs.height = screenHeight; + context = canvas.getContext('2d'); + bufferCtx = bufferCvs.getContext('2d'); + + var cx = canvas.width * 0.5, + cy = canvas.height * 0.5; + + grad = context.createRadialGradient(cx, cy, 0, cx, cy, Math.sqrt(cx * cx + cy * cy)); + grad.addColorStop(0, 'rgba(0, 0, 0, 0)'); + grad.addColorStop(1, 'rgba(0, 0, 0, 0.35)'); + } + + function mouseMove(e) { + mouse.set(e.clientX, e.clientY); + + var i, g, hit = false; + for (i = gravities.length - 1; i >= 0; i--) { + g = gravities[i]; + if ((!hit && g.hitTest(mouse)) || g.dragging) + g.isMouseOver = hit = true; + else + g.isMouseOver = false; + } + + canvas.style.cursor = hit ? 'pointer' : 'default'; + } + + function mouseDown(e) { + for (var i = gravities.length - 1; i >= 0; i--) { + if (gravities[i].isMouseOver) { + gravities[i].startDrag(mouse); + return; + } + } + gravities.push(new GravityPoint(e.clientX, e.clientY, G_POINT_RADIUS, { + particles: particles, + gravities: gravities + })); + } + + function mouseUp(e) { + for (var i = 0, len = gravities.length; i < len; i++) { + if (gravities[i].dragging) { + gravities[i].endDrag(); + break; + } + } + } + + function doubleClick(e) { + for (var i = gravities.length - 1; i >= 0; i--) { + if (gravities[i].isMouseOver) { + gravities[i].collapse(); + break; + } + } + } + + + // Functions + + function addParticle(num) { + var i, p; + for (i = 0; i < num; i++) { + p = new Particle( + Math.floor(Math.random() * screenWidth - PARTICLE_RADIUS * 2) + 1 + PARTICLE_RADIUS, + Math.floor(Math.random() * screenHeight - PARTICLE_RADIUS * 2) + 1 + PARTICLE_RADIUS, + PARTICLE_RADIUS + ); + p.addSpeed(Vector.random()); + particles.push(p); + } + } + + function removeParticle(num) { + if (particles.length < num) num = particles.length; + for (var i = 0; i < num; i++) { + particles.pop(); + } + } + + + // GUI Control + + control = { + particleNum: 100 + }; + + + // Init + + canvas = document.getElementById('c'); + bufferCvs = document.createElement('canvas'); + + window.addEventListener('resize', resize, false); + resize(null); + + addParticle(control.particleNum); + + canvas.addEventListener('mousemove', mouseMove, false); + canvas.addEventListener('mousedown', mouseDown, false); + canvas.addEventListener('mouseup', mouseUp, false); + canvas.addEventListener('dblclick', doubleClick, false); + + + // GUI + + gui = new dat.GUI(); + gui.add(control, 'particleNum', 0, 500).step(1).name('Particle Num').onChange(function() { + var n = (control.particleNum | 0) - particles.length; + if (n > 0) + addParticle(n); + else if (n < 0) + removeParticle(-n); + }); + gui.add(GravityPoint, 'interferenceToPoint').name('Interference Between Point'); + gui.close(); + + + // Start Update + + var loop = function() { + var i, len, g, p; + + context.save(); + context.fillStyle = BACKGROUND_COLOR; + context.fillRect(0, 0, screenWidth, screenHeight); + context.fillStyle = grad; + context.fillRect(0, 0, screenWidth, screenHeight); + context.restore(); + + for (i = 0, len = gravities.length; i < len; i++) { + g = gravities[i]; + if (g.dragging) g.drag(mouse); + g.render(context); + if (g.destroyed) { + gravities.splice(i, 1); + len--; + i--; + } + } + + bufferCtx.save(); + bufferCtx.globalCompositeOperation = 'destination-out'; + bufferCtx.globalAlpha = 0.35; + bufferCtx.fillRect(0, 0, screenWidth, screenHeight); + bufferCtx.restore(); + + // パーティクルをバッファに描画 + // for (i = 0, len = particles.length; i < len; i++) { + // particles[i].render(bufferCtx); + // } + len = particles.length; + bufferCtx.save(); + bufferCtx.fillStyle = bufferCtx.strokeStyle = '#fff'; + bufferCtx.lineCap = bufferCtx.lineJoin = 'round'; + bufferCtx.lineWidth = PARTICLE_RADIUS * 2; + bufferCtx.beginPath(); + for (i = 0; i < len; i++) { + p = particles[i]; + p.update(); + bufferCtx.moveTo(p.x, p.y); + bufferCtx.lineTo(p._latest.x, p._latest.y); + } + bufferCtx.stroke(); + bufferCtx.beginPath(); + for (i = 0; i < len; i++) { + p = particles[i]; + bufferCtx.moveTo(p.x, p.y); + bufferCtx.arc(p.x, p.y, p.radius, 0, Math.PI * 2, false); + } + bufferCtx.fill(); + bufferCtx.restore(); + + // バッファをキャンバスに描画 + context.drawImage(bufferCvs, 0, 0); + + requestAnimationFrame(loop); + }; + loop(); + +})(); + diff --git a/src/gravity-points/index.html b/src/gravity-points/index.html new file mode 100644 index 0000000..28147b8 --- /dev/null +++ b/src/gravity-points/index.html @@ -0,0 +1,15 @@ + + +
+ +