複数の円の衝突と跳ね返り3D|HTML5

複数の円の衝突と跳ね返り3D|Circles Collision and Rebounding 3D

複数の円の衝突と跳ね返り3D|HTML5 : デモ

複数の円の衝突と跳ね返り3D|HTML5 : ZIPファイル(4kb)

1.HTML

<!DOCTYPE html>
<html lang="jp">
    <head>
        <meta charset="UTF-8" />
        <title>複数の円の跳ね返り3D</title>
        <link href="css/base.css" rel="stylesheet" type="text/css">
        <script src="js/base.js"></script>
    </head>
    <body>
        <div id="contents">
            <canvas id="canvas" width="275" height="275"></canvas>
        </div>
    </body>
</html>

2.CSS

@charset "utf-8";
 
body
{
    margin: 0;
    padding: 0;
    background-color: #fff;
}
 
#contents
{
    position: absolute;
    top: 0;
    left: 0;
    width: 275px;
    height: 275px;
    border: 1px solid #000;
    overflow:hidden;
}
 
#canvas
{
    position: absolute;
    top: 0;
    left: 0;
    width: 275px;
    height: 275px;
}

3.JavaScript

base.js

window.addEventListener("load", init, false);
 
function init()
{
    var offsetX = 0,
    offsetY = 0,
   	ctx,
   	balls = [],
	numBalls = 15,
	fl = 250,
	vpX = 0,
	vpY = 0,
	top = -150,
	bottom = 150,
	left = -150,
	right = 150,
	front = 150,
	back = -150,
   	theContents;
    
    theContents = document.getElementById("contents");
    offsetX = (theContents.currentStyle || document.defaultView.getComputedStyle(theContents,'')).width;
    offsetX = Number(offsetX.replace('px', ''));
 
    offsetY = (theContents.currentStyle || document.defaultView.getComputedStyle(theContents,'')).height;
    offsetY = Number(offsetY.replace('px', ''));
    
    vpX = offsetX / 2;
    vpY = offsetY / 2;
   	
   	ctx = document.getElementById('canvas').getContext("2d");
   	
   	for(var i = 0; i < numBalls; i++)
	{
		var radius = 15;
		balls[i] = 
		{
			radius : radius, 
			red : Math.floor(Math.random() * 256),
			green : Math.floor(Math.random() * 256),
		    blue : Math.floor(Math.random() * 256),
			vx : Math.random() * 10 - 5,
			vy : Math.random() * 10 - 5,
			vz : Math.random() * 10 - 5,
			x : i*100,
			y : i*50,
			xpos : 0,
			ypos : 0,
			zpos : 0,
			scaleX : 1,
			scaleY : 1,
			mass : radius,
			visible : false
		}
	}
	
   	setAnimation();
    
    function setAnimation()
	{
		animation();
		requestAnimationFrame(setAnimation);
	}
	
	function animation()
    {
    	ctx.clearRect(0, 0, 300, 300);

    	sortZ();
    	for(var i = 0; i < numBalls; i++)
    	{
    		var ball = balls[i];
    		move(ball);
    	}
		
		for(var i = 0; i < numBalls - 1; i++)
    	{
    		var ballA = balls[i];
    		for(var j = i+1; j < numBalls; j++)
	    	{
	    		var ballB = balls[j];
	    		checkCollisionX(ballA, ballB);
	    		checkCollisionY(ballA, ballB);
	    		//checkCollisionZ(ballA, ballB);
	    	}
    	}
    }
    
    function checkCollisionX(ball0, ball1)
    {
    	var dx = ball1.xpos - ball0.xpos;
		var dy = ball1.ypos - ball0.ypos;
		var dz = ball1.zpos - ball0.zpos;
		var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
		
		if(dist < ball0.radius + ball1.radius)
		{
			// 角度とサイン、コサインの計算
			var angle = Math.atan2(dy, dz);
			var sin = Math.sin(angle);
			var cos = Math.cos(angle);
			
			// ball0の位置の回転
			var pos0 = {zpos:0, ypos:0};
			
			// ball1の位置の回転
			var pos1 = rotateX(dz, dy, sin, cos, true);
			
			// ball0の速度の回転
			var vel0 = rotateX(ball0.vz, ball0.vy, sin, cos, true);
			
			// ball1の速度の回転
			var vel1 = rotateX(ball1.vz, ball1.vy, sin, cos, true);
			
			// 衝突反応
			var vzTotal = vel0.zpos - vel1.zpos;
			vel0.zpos = ((ball0.mass - ball1.mass) * vel0.zpos + 
			       2 * ball1.mass * vel1.zpos) / 
			          (ball0.mass + ball1.mass);
			vel1.zpos = vzTotal + vel0.zpos;

			// 位置の更新
			var absV = Math.abs(vel0.zpos) + Math.abs(vel1.zpos);
			var overlap = (ball0.radius + ball1.radius) 
			                      - Math.abs(pos0.zpos - pos1.zpos);
			pos0.zpos += vel0.zpos / absV * overlap;
			pos1.zpos += vel1.zpos / absV * overlap;
			
			// 座標の回転
			var pos0F = rotateX(pos0.zpos, pos0.ypos, sin, cos, false);
									  
			var pos1F = rotateX(pos1.zpos, pos1.ypos, sin, cos, false);

			// 実際の画面位置への調整
			ball1.zpos = ball0.zpos + pos1F.zpos;
			ball1.ypos = ball0.ypos + pos1F.ypos;
			ball0.zpos = ball0.zpos + pos0F.zpos;
			ball0.ypos = ball0.ypos + pos0F.ypos;
			
			// 速度の回転
			var vel0F = rotateX(vel0.zpos, vel0.ypos, sin, cos, false);
			var vel1F = rotateX(vel1.zpos, vel1.ypos, sin, cos, false);
			ball0.vz = vel0F.zpos;
			ball0.vy = vel0F.ypos;
			ball1.vz = vel1F.zpos;
			ball1.vy = vel1F.ypos;
		}
    }
    
    function checkCollisionY(ball0, ball1)
    {
    	var dx = ball1.xpos - ball0.xpos;
		var dy = ball1.ypos - ball0.ypos;
		var dz = ball1.zpos - ball0.zpos;
		var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
		
		if(dist < ball0.radius + ball1.radius)
		{
			// 角度とサイン、コサインの計算
			var angle = Math.atan2(dx, dz);
			var sin = Math.sin(angle);
			var cos = Math.cos(angle);
			
			// ball0の位置の回転
			var pos0 = {zpos:0, xpos:0};
			
			// ball1の位置の回転
			var pos1 = rotateY(dz, dx, sin, cos, true);
			
			// ball0の速度の回転
			var vel0 = rotateY(ball0.vz, ball0.vx, sin, cos, true);
			
			// ball1の速度の回転
			var vel1 = rotateY(ball1.vz, ball1.vx, sin, cos, true);
			
			// 衝突反応
			var vzTotal = vel0.zpos - vel1.zpos;
			vel0.zpos = ((ball0.mass - ball1.mass) * vel0.zpos + 
			       2 * ball1.mass * vel1.zpos) / 
			          (ball0.mass + ball1.mass);
			vel1.zpos = vzTotal + vel0.zpos;

			// 位置の更新
			var absV = Math.abs(vel0.zpos) + Math.abs(vel1.zpos);
			var overlap = (ball0.radius + ball1.radius) 
			                      - Math.abs(pos0.zpos - pos1.zpos);
			pos0.zpos += vel0.zpos / absV * overlap;
			pos1.zpos += vel1.zpos / absV * overlap;
			
			// 座標の回転
			var pos0F = rotateY(pos0.zpos, pos0.xpos, sin, cos, false);
									  
			var pos1F = rotateY(pos1.zpos, pos1.xpos, sin, cos, false);

			// 実際の画面位置への調整
			ball1.zpos = ball0.zpos + pos1F.zpos;
			ball1.xpos = ball0.xpos + pos1F.xpos;
			ball0.zpos = ball0.zpos + pos0F.zpos;
			ball0.xpos = ball0.xpos + pos0F.xpos;
			
			// 速度の回転
			var vel0F = rotateY(vel0.zpos, vel0.xpos, sin, cos, false);
			var vel1F = rotateY(vel1.zpos, vel1.xpos, sin, cos, false);
			ball0.vz = vel0F.zpos;
			ball0.vx = vel0F.xpos;
			ball1.vz = vel1F.zpos;
			ball1.vx = vel1F.xpos;
		}
    }
    
    function checkCollisionZ(ball0, ball1)
    {
    	var dx = ball1.xpos - ball0.xpos;
		var dy = ball1.ypos - ball0.ypos;
		var dz = ball1.zpos - ball0.zpos;
		var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
		
		if(dist < ball0.radius + ball1.radius)
		{
			// 角度とサイン、コサインの計算
			var angle = Math.atan2(dy, dx);
			var sin = Math.sin(angle);
			var cos = Math.cos(angle);
			
			// ball0の位置の回転
			var pos0 = {xpos:0, ypos:0};
			
			// ball1の位置の回転
			var pos1 = rotateZ(dx, dy, sin, cos, true);
			
			// ball0の速度の回転
			var vel0 = rotateZ(ball0.vx, ball0.vy, sin, cos, true);
			
			// ball1の速度の回転
			var vel1 = rotateZ(ball1.vx, ball1.vy, sin, cos, true);
			
			// 衝突反応
			var vxTotal = vel0.xpos - vel1.xpos;
			vel0.xpos = ((ball0.mass - ball1.mass) * vel0.xpos + 
			       2 * ball1.mass * vel1.xpos) / 
			          (ball0.mass + ball1.mass);
			vel1.xpos = vxTotal + vel0.xpos;

			// 位置の更新
			var absV = Math.abs(vel0.xpos) + Math.abs(vel1.xpos);
			var overlap = (ball0.radius + ball1.radius) 
			                      - Math.abs(pos0.xpos - pos1.xpos);
			pos0.xpos += vel0.xpos / absV * overlap;
			pos1.xpos += vel1.xpos / absV * overlap;
			
			// 座標の回転
			var pos0F = rotateZ(pos0.xpos, pos0.ypos, sin, cos, false);
									  
			var pos1F = rotateZ(pos1.xpos, pos1.ypos, sin, cos, false);

			// 実際の画面位置への調整
			ball1.xpos = ball0.xpos + pos1F.xpos;
			ball1.ypos = ball0.ypos + pos1F.ypos;
			ball0.xpos = ball0.xpos + pos0F.xpos;
			ball0.ypos = ball0.ypos + pos0F.ypos;
			
			// 速度の回転
			var vel0F = rotateZ(vel0.xpos, vel0.ypos, sin, cos, false);
			var vel1F = rotateZ(vel1.xpos, vel1.ypos, sin, cos, false);
			ball0.vx = vel0F.xpos;
			ball0.vy = vel0F.ypos;
			ball1.vx = vel1F.xpos;
			ball1.vy = vel1F.ypos;
		}
    }
    
    function rotateX(zpos, ypos, sin, cos, reverse)
	{
		var result = {zpos:0, ypos:0};
		if(reverse)
		{
			result.ypos = ypos * cos - zpos * sin;
			result.zpos = zpos * cos + ypos * sin;
		}
		else
		{
			result.ypos = ypos * cos + zpos * sin;
			result.zpos = zpos * cos - ypos * sin;
		}
		return result;
	}
	
	function rotateY(zpos, xpos, sin, cos, reverse)
	{
		var result = {zpos:0, xpos:0};
		if(reverse)
		{
			result.xpos = xpos * cos - zpos * sin;
			result.zpos = zpos * cos + xpos * sin;
		}
		else
		{
			result.xpos = xpos * cos + zpos * sin;
			result.zpos = zpos * cos - xpos * sin;
		}
		return result;
	}
    
    function rotateZ(xpos, ypos, sin, cos, reverse)
	{
		var result = {xpos:0, ypos:0};
		if(reverse)
		{
			result.xpos = xpos * cos + ypos * sin;
			result.ypos = ypos * cos - xpos * sin;
		}
		else
		{
			result.xpos = xpos * cos - ypos * sin;
			result.ypos = ypos * cos + xpos * sin;
		}
		return result;
	}
    
    function move(ball)
	{
		var radius =  ball.radius;
		
		ball.xpos += ball.vx;
		ball.ypos += ball.vy;
		ball.zpos += ball.vz;
		
		if(ball.xpos + radius > right)
		{
			ball.xpos = right - radius;
			ball.vx *= -1;
		}
		else if(ball.xpos - radius < left)
		{
			ball.xpos = left + radius;
			ball.vx *= -1;
		}
		if(ball.ypos + radius > bottom)
		{
			ball.ypos = bottom - radius;
			ball.vy *= -1;
		}
		else if(ball.ypos - radius < top)
		{
			ball.ypos = top + radius;
			ball.vy *= -1;
		}
		if(ball.zpos + radius > front)
		{
			ball.zpos = front - radius;
			ball.vz *= -1;
		}
		else if(ball.zpos - radius < back)
		{
			ball.zpos = back + radius;
			ball.vz *= -1;
		}
		
		if(ball.zpos > -fl)
		{
			var scale = fl / (fl + ball.zpos);
			ball.scaleX = scale;
			ball.scaleY = scale;
			ball.x = vpX + ball.xpos * scale;
			ball.y = vpY + ball.ypos * scale;
			ball.visible = true;
		}
		else
		{
			ball.visible = false;
		}
		
		if(ball.visible)
		{
			ctx.strokeStyle ="rgb(" + ball.red + "," + ball.green + "," + ball.blue + ")";
		    ctx.lineWidth = 1;
		    ctx.beginPath();
		    ctx.arc(ball.x, ball.y, ball.radius * ball.scaleX, 0, Math.PI * 2, true);
		    ctx.fillStyle = "rgb(" + ball.red + "," + ball.green + "," + ball.blue + ")";
		    ctx.fill();
		    ctx.stroke();
		}
	}
	
	/*
	 * 配列の並べ替え: 降順
	 */
	function sortZ()
	{
		balls.sort( function( a, b ) { return b.zpos - a.zpos; } );
	}
}

// 各ブラウザ対応
window.requestAnimationFrame = (function()
{
	return window.requestAnimationFrame ||
	window.webkitRequestAnimationFrame ||
	window.mozRequestAnimationFrame ||
	window.oRequestAnimationFrame ||
	window.msRequestAnimationFrame ||
	function(callback)
	{
		window.setTimeout(callback, 1000/60);
	};
}());

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です