HTML5/Billiard 03

Billiard 03

Soul-Learner 2012. 4. 18. 14:03

당구공의 속도가 자연스럽게 감쇠하는 시뮬레이션

테스트 환경: HTML5, Eclipse, Google Chrome, Javascript

Ball.js

Vector2D.js

Canvas를 지원하지 않는 브라우저입니다


<!DOCTYPE html>

<html>

<head>

<meta charset="EUC-KR">

<title>Canvas를 이용한 애니메이션</title>

<style type="text/css">

 #MyCanvas { border:1px dotted black;}

</style>

<script type="text/javascript" src="Vector2D.js"></script>

<script type="text/javascript" src="Ball.js"></script>

<script type="text/javascript">

 var ctx = null;

 var redBall = null;

 var greenBall = null;

 var radius = 30;

 var timer = 0;

 var prevtime = 0;

 var currtime = 0;

 

 window.onload = function() {

  var canvas = document.getElementById("MyCanvas");

  ctx = canvas.getContext("2d");

  if(!ctx){

   alert("캔바스로부터 Context를 구할 수가 없었습니다");

   return;

  }

  

  init(); 

  gameLoop(); 

  timer = setInterval("gameLoop()",10);

 } 

 

 function init() {

  redBall = new Ball(ctx,"rgba(255,0,0,0.4)",new Vector2D(ctx.canvas.width/2, ctx.canvas.height/2),radius, new Vector2D(0,0));

  greenBall = new Ball(ctx,"rgba(0,255,0,0.4)",new Vector2D(ctx.canvas.width/2-50, ctx.canvas.height-30),radius, new Vector2D(0,-30));

 }

 

 function gameLoop() {

  currtime = new Date().getTime();

  if(prevtime==0) prevtime = currtime; 

  ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);

  redBall.update(currtime);

  greenBall.update(currtime);

  redBall.draw(ctx);

  greenBall.draw(ctx); 

  checkCollision();

  prevtime = currtime;

 } 

 

 function print(msg, vec) {

  document.write(msg+"("+vec.x+", "+vec.y+"), 크기("+vec.length()+")<br/>");

 }


 /* 서로 충돌한 공은 서로의 운동 에너지를 상대방에게 주고 받게 되므로 벡터연산을 두번 중복해야 한다 */

 function checkCollision() {

  var tmpVec = redBall.pos.sub(greenBall.pos);

  var colVec1 = null, colVec2 = null;

  var distance = tmpVec.length();


  if(distance<=radius*2) {

   colVec1 = tmpVec;

   while(redBall.pos.sub(greenBall.pos).length()<radius*2){

    redBall.pos.x -= redBall.speed.x;

    redBall.pos.y -= redBall.speed.y;

    greenBall.pos.x -= greenBall.speed.x;

    greenBall.pos.y -= greenBall.speed.y;

   }

   // 초록 공의 속도와 방향은 충돌시 적색공의 속도와 방향에 영향을 미친다

   colVec1.normalize();

   var greenSpeedLen = greenBall.speed.length();

   var cosVal = 0;

   if(greenSpeedLen!=0) {

    cosVal = greenBall.speed.dot(colVec1)/greenSpeedLen;

    colVec1.mul(cosVal*greenSpeedLen); // 초록공의 충돌 에너지를 표현한 벡터

   }

   // 적색 공의 속도와 방향은 충돌시 초록공의 속도와 방향에 영향을 미친다.

   var redSpeedLen = redBall.speed.length();

   if(redSpeedLen!=0) {

    colVec2 = greenBall.pos.sub(redBall.pos);

    colVec2.normalize();


    cosVal = redBall.speed.dot(colVec2)/redSpeedLen;

    colVec2.mul(cosVal*redSpeedLen); // 적색공의 충돌 에너지를 표현한 벡터

   }

   if(colVec1 != null) {

    redBall.speed = redBall.speed.add(colVec1);

    greenBall.speed = greenBall.speed.sub(colVec1);

   }

   if(colVec2 != null) {

    redBall.speed = redBall.speed.sub(colVec2);

    greenBall.speed = greenBall.speed.add(colVec2);

   }

  }

 }

</script>

</head>

<body>

<canvas id="MyCanvas" width="400" height="300">

 Canvas를 지원하지 않는 브라우저입니다

</canvas>

</body>

</html>


Vector2D.js

function Vector2D(x,y) {

 

 this.x = x;

 this.y = y;

 this.RAD = Math.PI/180;

 this.DEG = 180/Math.PI;

 

 Vector2D.prototype.add = add;

 Vector2D.prototype.sub = sub;

 Vector2D.prototype.length = length;

 Vector2D.prototype.normalize = normalize;

 Vector2D.prototype.mul = mul;

 Vector2D.prototype.dot = dot;

 Vector2D.prototype.angle = angle;

}


function add(vec){

 var x = this.x + vec.x;

 var y = this.y + vec.y;

 return new Vector2D(x,y);

}


function sub(vec) {

 var x = this.x-vec.x;

 var y = this.y-vec.y;

 return new Vector2D(x,y);

}


function length(){

 return Math.sqrt(this.x*this.x + this.y*this.y);

}


function normalize() {

 var len = this.length();

 this.x /= len;

 this.y /= len;

}


function mul(len) {

 this.x *= len;

 this.y *= len;

}


function dot(vec) {

 return (this.x*vec.x) + (this.y*vec.y);

}


function angle(vec) {

 var cosVal = this.dot(vec)/ (this.length()*vec.length());

 var rad = Math.acos(cosVal);

 //return rad * this.DEG;

 return rad;

}


function copy() {

 return new Vector2D(this.x, this.y);

}


Ball.js

function Ball(ctx, color, pos, r, speed) {

 this.ctx = ctx;

 this.color = color;

 this.pos = pos;

 this.r = r;

 this.speed = speed;

 this.prevtime = 0;


 /* 이동량을 초당 최대 200으로 제한하는 경우 */

 this.movRate = 200/1000;

 /* 밀리초당 감쇠량 */

 this.decRate = 1/1900;

 

 Ball.prototype.update = update;

 Ball.prototype.draw = draw;

};


function update(currtime){

 if(this.prevtime==0) {

  this.prevtime = currtime;

  return;

 }

 

 /* 업데이트 간 경과시간에 따른 이동량 및 감쇠량 계산 */

 var move = (currtime-this.prevtime) * this.movRate;

 var decrement = (currtime-this.prevtime) * this.decRate;

 //console.log("decrement:"+decrement);

 

 this.prevtime = currtime;

 

 /* 속도가 무한히 작아지도록 두지 않고 적당한 시점에서 속도를 0으로 설정함 */

 if(this.speed.length()<0.01) {

  this.speed.x = 0;

  this.speed.y = 0;

  return;

 }

 

 // 속도의 자연감쇠 효과

 this.speed.x *= this.speed.x==0 ? 0 : (1-decrement);

 this.speed.y *= this.speed.y==0 ? 0 : (1-decrement);

 

 this.pos.x += this.speed.x*(move/10);

 this.pos.y += this.speed.y*(move/10);


 // 캔바스의 테두리에 닿으면 반대 방향으로 반사한다

 if(this.pos.x+this.r >= this.ctx.canvas.width){

  this.speed.x *= -1;

  this.pos.x = this.ctx.canvas.width-this.r;

 }

 if(this.pos.y+this.r >= this.ctx.canvas.height){

  this.speed.y *= -1;

  this.pos.y = this.ctx.canvas.height-this.r;

 }

 if(this.pos.x-this.r <= 0) {

  this.speed.x *= -1;

  this.pos.x = this.r;

 }

 if(this.pos.y-this.r <= 0) {

  this.speed.y *= -1;

  this.pos.y = this.r;

 }

};


function draw(){

 this.ctx.beginPath();

 this.ctx.fillStyle = this.color;

 this.ctx.arc(this.pos.x, this.pos.y, this.r, 0, 2 * Math.PI);

 this.ctx.fill();

 

 this.ctx.lineWidth = 1;

 this.ctx.strokeStyle = this.speed.length()==0 ? "black" : "rgba(0,0,0,0)";

 this.ctx.stroke();

};