闲着无聊写着玩的一个小程序,模拟多个平面小球在相互引力作用下的运动状态。随便写的也没怎么优化计算过程,在chrome 31,ff 25,ie 10上都运行了下,50个小球在1000频率的情况下chrome和ff还能看看,ie就完全不能看了。
源码如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta http-equiv="content-language" content="zh-CN" />
<title>引力小球运动探测</title>
<script type="text/javascript">
//---------------------------------------------------此区域定义程序执行需要的bean结构-----------------------
function Vector(speedx,speedy){
this.speedX=speedx;
this.speedY=speedy;
}
/**
x:x坐标
y:y坐标
weight:质量,与引力计算有关
objSpeed:一个向量,包含x方向和y方向的速度
*/
function OriantObj(x,y,r,weight,objSpeed){
this.x=x;
this.y=y;
this.r=r;
this.speed=objSpeed;
this.weight=weight;
}
function pathCalc(speed,accelerate,time){
var path=new Vector();
path.speedX=speed.speedX*time+accelerate.speedX*time*time/2;
path.speedY=speed.speedY*time+accelerate.speedY*time*time/2;
return path;
}
function speedCalc(speed,accelerate,time){
var path=new Vector();
path.speedX=speed.speedX+accelerate.speedX*time;
path.speedY=speed.speedY+accelerate.speedY*time;
return path;
}
function Position(x,y){
this.px=x;
this.py=y;
}
</script>
<script type="text/javascript">
//-----------------------------------------------------------此区域定义计算过程中用到的各种函数---------------------------
/**
计算令一个小球对当前小球作用力的加速度效果;
*/
OriantObj.prototype.accelerationToOriant=function(another){
var rx=another.x-this.x;
var ry=another.y-this.y;
var r2=rx*rx+ry*ry;
var r=Math.sqrt(r2);
//防止无限加速的情况产生
var minr=this.r+another.r;
if(r<minr){
r=minr;
r2=minr*minr;
}
var accs=GRAVITY*another.weight/(r2);
return new Vector(rx*accs/r,ry*accs/r);
};
/**
计算一小段时间内小球运动到的地方,在此时间内认为各小球位置变动极小,忽略引力变化
同时,如果判断到两小球碰撞到一起了,认定两球均为刚体,计算碰撞效果;
*/
function caclWhile(oriants,index,ft){
var currAcc=new Vector(0,0);
var ball=oriants[index];
for(var kk=0;oriants[kk];kk++){
if(kk===index){
continue;
}
var rr=ball.r+oriants[kk].r;
var lenx=ball.x-oriants[kk].x;
var leny=ball.y-oriants[kk].y;
//计算引力效果
if(openGravity){
var accCurr=ball.accelerationToOriant(oriants[kk]);
currAcc.speedX=currAcc.speedX+accCurr.speedX;
currAcc.speedY=currAcc.speedY+accCurr.speedY;
}
//计算碰撞效果
if((lenx*lenx+leny*leny)<=(rr*rr)){
rigidBodyCollision(ball,oriants[kk]);
}
}
if(openFriction){
//计算阻力作用效果
currAcc.speedX=frictionCacl(ball.speed.speedX,currAcc.speedX);
currAcc.speedY=frictionCacl(ball.speed.speedY,currAcc.speedY);
}
var pp=pathCalc(ball.speed,currAcc,ft);
ball.x=ball.x+pp.speedX;
ball.y=ball.y+pp.speedY;
var sbf=ball.speed;
ball.speed=speedCalc(ball.speed,currAcc,ft);
//如果速度变更跨过0,则置0
if(sbf.speedX*ball.speed.speedX<0){
ball.speed.speedX=0;
}
if(sbf.speedY*ball.speed.speedY<0){
ball.speed.speedY=0;
}
//边界反弹检测
if(openRegionDetect){
drawRegionDetect(ball);
}
return ball;
}
/**
计算阻力作用
*/
function frictionCacl(speed,force){
if(speed==0){
if(FRICTION>Math.abs(force))
return 0;
else
return force-(FRICTION*force)/Math.abs(force);
}else{
return force-(FRICTION*speed)/Math.abs(speed)
}
}
/**
计算刚体碰撞
*/
function rigidBodyCollision(obj1,obj2){
//避免重复检测碰撞
if(obj1.crashChecked&&obj2.crashChecked){
obj1.crashChecked=obj2.crashChecked=false;
return ;
}
if(obj1.speed.speedX==0&&obj1.speed.speedY==0&&obj2.speed.speedX==0&&obj2.speed.speedY==0){
return;
}
var cx=obj1.x-obj2.x,cy=obj1.y-obj2.y;
var tmp=cx*cx+cy*cy;
var x1=cx*obj1.speed.speedX+cy*obj1.speed.speedY;
var y1=-cy*obj1.speed.speedX+cx*obj1.speed.speedY;
var x2=cx*obj2.speed.speedX+cy*obj2.speed.speedY;
var y2=-cy*obj2.speed.speedX+cx*obj2.speed.speedY;
var midtmp=(1+rebound)*(obj1.weight*x1+obj2.weight*x2)/(obj1.weight+obj2.weight);
var ax1=midtmp-rebound*x1;
var ax2=midtmp-rebound*x2;
obj1.speed.speedX=(ax1*cx-y1*cy)/tmp;
obj1.speed.speedY=(ax1*cy+y1*cx)/tmp;
obj2.speed.speedX=(ax2*cx-y2*cy)/tmp;
obj2.speed.speedY=(ax2*cy+y2*cx)/tmp;
obj1.crashChecked=obj2.crashChecked=true;
//按重量各退一步,避免互相嵌入
var axxx=((obj1.r+obj2.r)/Math.sqrt(tmp))-1;
var t1=axxx/(1+(obj2.weight/obj1.weight));
var t2=axxx/(1+(obj1.weight/obj2.weight));
obj1.x=obj1.x+cx*t1;
obj1.y=obj1.y+cy*t1;
obj2.x=obj2.x-cx*t2;
obj2.y=obj2.y-cy*t2;
}
/**
边界检测函数
*/
function drawRegionDetect(ball){
if((ball.x-ball.r<0&&ball.speed.speedX<0)||(ball.x+ball.r>900&&ball.speed.speedX>0)){
ball.speed.speedX=-ball.speed.speedX;
}
if((ball.y-ball.r<0&&ball.speed.speedY<0)||(ball.y+ball.r>500&&ball.speed.speedY>0)){
ball.speed.speedY=-ball.speed.speedY;
}
}
function strIsNum(str){
return !isNaN(str);
}
</script>
<script type="text/javascript">
//--------------------------------------------此区域定义运行参数------------------------------------------------------
var GRAVITY=0.667;//引力系数
var ft=0.00001;//两次运动计算的间隔时间,在该段时间内忽略引力变化
var drawSpeed=1000;//时间轴运行速度,该值越大,两次绘图间代表的实际时间越长,具体时间为drawSpeed*ft
var rebound=0.91;//定义碰撞恢复系数
var FRICTION=1.96;//定义摩擦系数
var openGravity=false;//是否开启引力效果
var openFriction=false;//是否开启摩擦力效果
var openRegionDetect=false;//是否开启边界反弹效果
var runAnimation=false;//是否要运行动画
var defaultBallColor="blue";//设置小球颜色为红色
var oriantSize=50,oriantR=8;//设置小球个数与半径
//定义多个刚性引力球
var oriants=new Array();
//随机生成多个刚体球
for(var i=0;i<oriantSize;i++){
while(true){
var xy=[Math.random()*900,Math.random()*500];
var cc=true;
for(var j=0;oriants[j];j++){
if((oriants[j].x-xy[0])*(oriants[j].x-xy[0])+(oriants[j].y-xy[1])*(oriants[j].y-xy[1])<((oriantR*oriantR)<<2)){
cc=false;
break;;
}
}
if(cc){
oriants[i]=new OriantObj(xy[0],xy[1],oriantR,Math.random()*10e5,new Vector(0,0));
break;
}
}
}
</script>
<style type="text/css">
.tabhead{
width:25%;
}
.tabinput{
width:25%;
margin:0;
text-align:left;
}
</style>
</head>
<body style="background:black;color:white;">
<table>
<tr>
<td width="901" height="501">
<canvas id="drawReagon" contenteditable="false" height="500" width="900" style="border: 1px solid white;margin: 0;"></canvas>
</td>
<td valign="top" width="360">
<table>
<tr>
<td class="tabhead">开启引力</td>
<td class="tabinput" align="left">
<input type="checkbox" οnclick="javascript:openGravity=this.checked;">
</td>
<td class="tabhead">引力系数</td>
<td class="tabinput">
<input type="text" id="gravityIndex" width="50px" style="width:50px;" οnblur="javascript:if(strIsNum(this.value)){GRAVITY=Number(this.value);}else{alert('引力系数必须为数字!');this.value=GRAVITY=0.667};">
</td>
</tr>
<tr>
<td class="tabhead">开启摩擦</td>
<td class="tabinput" align="left">
<input type="checkbox" οnclick="javascript:openFriction=this.checked;">
</td>
<td class="tabhead">摩擦系数</td>
<td class="tabinput">
<input type="text" id="frictionIndex" style="width:50px;"
οnblur="javascript:if(strIsNum(this.value)){FRICTION=Number(this.value);}else{alert('摩擦系数必须为数字!');this.value=FRICTION=1.96};">
</td>
</tr>
<tr>
<td class="tabhead">边界反弹</td>
<td class="tabinput" align="left">
<input type="checkbox" οnclick="javascript:openRegionDetect=this.checked;">
</td>
<td class="tabhead">计算频率</td>
<td class="tabinput">
<input type="text" id="drawSpeedIndex" width="50px" style="width:50px;"
οnblur="javascript:if(strIsNum(this.value)){drawSpeed=Number(this.value);}else{alert('计算频率必须为数字!');this.value=drawSpeed=1000};if(drawSpeed<1){alert('计算频率过慢!');this.value=drawSpeed=1000}">
</td>
</tr>
<tr>
<td class="tabhead" colspan="3" align="right">球体恢复系数</td>
<td class="tabinput" align="left">
<input type="text" id="reboundIndex" width="50px" style="width:50px;"
οnblur="javascript:if(strIsNum(this.value)&&this.value>=0&&this.value<=1){rebound=Number(this.value);}else{alert('恢复系数必须为0-1之间的数字!');this.value=rebound=0.91};">
</td>
</tr>
<tr>
<td colspan="4" align="center">
<input type="button" id="runAnimationButton" style="width:80px;"
οnclick="javascript:if(runAnimation){this.value='继续运行';runAnimation=false;}else{this.value='暂停运行';runAnimation=true;};">
</td>
</tr>
</table>
</td>
</tr>
</table>
<script type="text/javascript">
function $(id){return document.getElementById(id);}
var cs=$("drawReagon").getContext("2d");
cs.fillStyle = defaultBallColor;
$("gravityIndex").value=GRAVITY;
$("frictionIndex").value=FRICTION;
$("drawSpeedIndex").value=drawSpeed;
$("reboundIndex").value=rebound;
$("runAnimationButton").value=runAnimation?"暂停运行":"继续运行";
if(!runAnimation){
for(var i=0;oriants[i];i++){
cs.beginPath();
cs.arc(oriants[i].x,oriants[i].y,oriants[i].r,0,Math.PI * 2,false);
cs.fill();
cs.closePath()
}
}
//计算和绘制界面
(function funSelf(){
if(runAnimation){
cs.clearRect(0,0,900,1000);
for(var i=0;i<drawSpeed;i++){
for(var ks=0;oriants[ks];ks++){
oriants[ks]=caclWhile(oriants,ks,ft);
}
}
for(var i=0;oriants[i];i++){
cs.beginPath();
cs.arc(oriants[i].x,oriants[i].y,oriants[i].r,0,Math.PI * 2,false);
cs.fill();
cs.closePath()
}
}
window.requestAnimationFrame(funSelf);
})();
</script>
</body>
</html>