• Flash/Flex学习笔记(43):动量守恒与能量守恒


    动能公式:

    动量公式:

    动量守恒:

    能量守恒:

    根据这些规律可以得到下列方程组:

    解该方程组,得到下面的公式:

    把这二个公式相减,可以得到:

    即:

    我们也经常利用这个公式简化运算

    基本的动量守恒演示:

    先给ball类添加一个质量"属性"

    package {
    	import flash.display.Sprite;
    
    	//小球 类
    	public class Ball extends Sprite {
    
    		public var radius:uint;//半径
    		public var color:uint;//颜色
    		public var vx:Number=0;//x轴速度
    		public var vy:Number=0;//y轴速度
    		public var count:uint=0;//辅助计数变量
    		public var isDragged=false;//是否正在被拖动
    		public var vr:Number=0;//旋转速度
    		public var mass:Number = 1;//质量
    
    		public function Ball(r:Number=50,c:uint=0xff0000) {
    			this.radius=r;
    			this.color=c;
    			init();
    		}
    
    		private function init():void {
    			graphics.beginFill(color);
    			graphics.drawCircle(0,0,radius);
    			graphics.endFill();
    		}
    	}
    }
    

    一维单轴刚体碰撞测试:

    package {
    	import flash.display.Sprite;
    	import flash.events.Event;
    	public class Billiard1 extends Sprite {
    		private var ball0:Ball;
    		private var ball1:Ball;
    		private var bounce:Number = -0.6;
    		public function Billiard1() {
    			init();
    		}
    		
    		private function init():void {
    			ball0=new Ball(40);			
    			addChild(ball0);
    			ball1=new Ball(20,0x0000ff);			
    			addChild(ball1);					
    			ReStart();
    		}
    		
    		private function ReStart():void{
    			ball0.mass=2;
    			ball0.x=50;
    			ball0.y=stage.stageHeight/2;
    			ball0.vx=5;
    			ball1.mass=1;
    			ball1.x=300;
    			ball1.y=stage.stageHeight/2;
    			ball1.vx=-5;		
    			addEventListener(Event.ENTER_FRAME,EnterFrameHandler);	
    		}
    		
    		private function EnterFrameHandler(event:Event):void {
    			ball0.x+=ball0.vx;
    			ball1.x+=ball1.vx;
    			var dist:Number=ball1.x-ball0.x;
    			
    			//如果撞到了
    			if (Math.abs(dist)<ball0.radius+ball1.radius) {
    				var vdx:Number = ball0.vx - ball1.vx;
    				var vx0Final:Number=((ball0.mass-ball1.mass)*ball0.vx + 2*ball1.mass*ball1.vx)/(ball0.mass+ball1.mass);
    				var vx1Final:Number= vx0Final + vdx;
    				ball0.vx=vx0Final;
    				ball1.vx=vx1Final;
    				
    				//不加下面这二句的话,从视觉效果上看,有可能会看到二个球相互撞入对方球体内了,这样就不符合物理学"刚体"模型的定义
    				ball0.x+=ball0.vx;
    				ball1.x+=ball1.vx;
    			}
    			
    			//舞台边界反弹
    			if (ball0.x >=stage.stageWidth-ball0.radius || ball0.x<=ball0.radius){
    				ball0.x -= ball0.vx;
    				ball0.vx *= bounce;
    			}			
    			
    			if (ball1.x >=stage.stageWidth-ball1.radius || ball1.x<=ball1.radius){
    				ball1.x -= ball1.vx;
    				ball1.vx *= bounce;
    			}
    			
    			trace(ball1.vx,ball0.vx);
    			
    			//如果二球都停了
    			if (Math.abs(ball1.vx)<=0.05 && Math.abs(ball0.vx)<=0.05){
    				removeEventListener(Event.ENTER_FRAME,EnterFrameHandler);	
    				ReStart();
    			}
    		}
    	}
    
    }
    

    二维坐标上的刚体碰撞:

    先来看这张图,红球a以Va速度运动,蓝球b以Vb速度运动,二球的连线正好与x轴平行(即:水平对心碰撞),碰撞的过程可以理解为二球水平速度分量Vax,Vbx应用运量守恒与能力守恒的结果(y轴方向的速度不受影响!)

    但很多情况下,二球的连线并非总是与坐标轴平行,比如下面这样:

    思路:仍然利用坐标旋转,先将二个球反向旋转到连线水平位置,然后按常规方式处理,完事后再旋转回来。

    var ballA:Ball=new Ball(80,Math.random()*0xffffff);
    var ballB:Ball=new Ball(50,Math.random()*0xffffff);
    var bounce:Number=-1;
    
    ballA.x=ballA.radius+100;
    ballB.x=ballA.radius+200;
    ballA.y=120;
    ballB.y=300;
    
    ballA.mass=2;
    ballB.mass=1;
    
    ballA.vx = 5*(Math.random()*2-1);
    ballB.vx = 5*(Math.random()*2-1);
    ballA.vy = 5*(Math.random()*2-1);
    ballB.vy = 5*(Math.random()*2-1);
    
    addChild(ballA);
    addChild(ballB);
    
    addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
    
    function EnterFrameHandler(e:Event):void {
    	ballA.x+=ballA.vx;
    	ballA.y+=ballA.vy;
    	ballB.x+=ballB.vx;
    	ballB.y+=ballB.vy;
    	
    	//运量守恒处理开始
    	var dx:Number=ballB.x-ballA.x;
    	var dy:Number=ballB.y-ballA.y;
    	var dist:Number=Math.sqrt(dx*dx+dy*dy);
    	if (dist<(ballA.radius + ballB.radius)) {
    		var angle:Number=Math.atan2(dy,dx);
    		var cos:Number=Math.cos(angle);
    		var sin:Number=Math.sin(angle);
    
    		//以ballA中心为旋转中心反向旋转
    		var xA:Number=0;//ballA自身为旋转中心,所以自身旋转后的相对坐标都是0
    		var yA:Number=0;
    
    		var xB:Number=dx*cos+dy*sin;
    		var yB:Number=dy*cos-dx*sin;
    
    		//先(反向)旋转二球相对(ballA的)速度
    		var vxA=ballA.vx*cos+ballA.vy*sin;
    		var vyA=ballA.vy*cos-ballA.vx*sin;
    		var vxB=ballB.vx*cos+ballB.vy*sin;
    		var vyB=ballB.vy*cos-ballB.vx*sin;
    
    		//旋转后的vx速度处理运量守恒
    		var vdx=vxA-vxB;
    		var vxAFinal = ((ballA.mass - ballB.mass)*vxA + 2*ballB.mass*vxB)/(ballA.mass + ballB.mass);
    		var vxBFinal=vxAFinal+vdx;
    
    		//相对位置处理
    		xA+=vxAFinal;
    		xB+=vxBFinal;
    
    		//处理完了,再旋转回去
    		//先处理坐标位置
    		var xAFinal:Number=xA*cos-yA*sin;
    		var yAFinal:Number=yA*cos+xA*sin;
    		var xBFinal:Number=xB*cos-yB*sin;
    		var yBFinal:Number=yB*cos+xB*sin;
    
    		//处理最终的位置变化
    		ballB.x=ballA.x+xBFinal;
    		ballB.y=ballA.y+yBFinal;
    		ballA.x+=xAFinal;
    		ballA.y+=yAFinal;
    
    		//再处理速度
    		ballA.vx=vxAFinal*cos-vyA*sin;
    		ballA.vy=vyA*cos+vxAFinal*sin;
    		ballB.vx=vxBFinal*cos-vyB*sin;
    		ballB.vy=vyB*cos+vxBFinal*sin;
    	}
    	//<--- 运量守恒处理结束
    	
    	CheckBounds(ballA);
    	CheckBounds(ballB);
    }
    
    //舞台边界检测
    function CheckBounds(b:Ball) {
    	if (b.x<b.radius) {
    		b.x=b.radius;
    		b.vx*=bounce;
    	} else if (b.x>stage.stageWidth-b.radius) {
    		b.x=stage.stageWidth-b.radius;
    		b.vx*=bounce;
    	}
    
    	if (b.y<b.radius) {
    		b.y=b.radius;
    		b.vy*=bounce;
    	} else if (b.y>stage.stageHeight-b.radius) {
    		b.y=stage.stageHeight-b.radius;
    		b.vy*=bounce;
    	}
    }
    

    粘连问题:

    反复运行上面这段动画,偶尔可能会发现二个球最终粘在一起,无法分开了,造成这种原因的情况很多,下面的示意图分析了可能的形成原因之一

    解决思路:找出重叠部分,然后把二个小球同时反向移动适当距离,让二个球分开即可

    先来一段测试代码:验证一下是否有效

    var ballA:Ball=new Ball(80,0xff0000);
    ballA.x=stage.stageWidth/2;
    ballA.y=stage.stageHeight/2;
    addChild(ballA);
    
    var ballB:Ball=new Ball(60,0x00ff00);
    ballB.x=stage.stageWidth/2-70;
    ballB.y=stage.stageHeight/2;
    addChild(ballB);
    
    btn1.x=stage.stageWidth/2;
    btn1.y=stage.stageHeight-btn1.height;
    btn1.addEventListener(MouseEvent.MOUSE_DOWN,MouseDownHandler);
    
    function MouseDownHandler(e:MouseEvent):void {
    	var overlap:Number=ballA.radius+ballB.radius-Math.abs(ballA.x-ballB.x);//计算重叠部分
    	trace(overlap);
    	
    	//计算每个球所占重叠部分中的比例
    	var aRadio:Number = ballA.radius/(ballA.radius + ballB.radius);
    	var bRadio:Number = ballB.radius/(ballA.radius + ballB.radius);
    	
    	//分离判断
    	if (overlap>0){
    		if (ballA.x>ballB.x){
    			ballA.x += overlap*aRadio;
    			ballB.x -= overlap*bRadio;
    		}
    		else{
    			ballA.x -= overlap*aRadio;
    			ballB.x += overlap*bRadio;
    		}
    	}
    }
    
    ballA.addEventListener(MouseEvent.MOUSE_DOWN,startDragHandler);
    ballB.addEventListener(MouseEvent.MOUSE_DOWN,startDragHandler);
    ballA.addEventListener(MouseEvent.MOUSE_OVER,MouseOverHandler);
    ballA.addEventListener(MouseEvent.MOUSE_OUT,MouseOutHandler);
    ballB.addEventListener(MouseEvent.MOUSE_OVER,MouseOverHandler);
    ballB.addEventListener(MouseEvent.MOUSE_OUT,MouseOutHandler);
    
    stage.addEventListener(MouseEvent.MOUSE_UP,stopDragHandler);
    
    var obj:Ball;
    var rect:Rectangle = new Rectangle(0,stage.stageHeight/2,stage.stageWidth,0);
    function startDragHandler(e:MouseEvent):void {
    	Mouse.cursor = MouseCursor.HAND;
    	obj=e.currentTarget as Ball;
    	obj.startDrag();
    }
    
    function stopDragHandler(e:MouseEvent):void {
    	if (obj!=null) {
    		obj.stopDrag(true,rect);
    		obj=null;
    		Mouse.cursor = MouseCursor.AUTO;
    	}
    }
    
    function MouseOverHandler(e:MouseEvent):void{
    	Mouse.cursor = MouseCursor.HAND;
    }
    
    function MouseOutHandler(e:MouseEvent):void{
    	Mouse.cursor = MouseCursor.AUTO;
    }
    

    水平拖动小球故意让它们重叠,然后点击“分开”按钮测试一下,ok,管用了!

    再回过头来解决运量守恒中的粘连问题:

    只要把EnterFrameHandler中的

     //相对位置处理  
    
     xA+=vxAFinal;  
    
     xB+=vxBFinal;  
    
    

    换成:

    //相对位置处理(同时要防止粘连)
    //xA+=vxAFinal;
    //xB+=vxBFinal;
    var sumRadius = ballA.radius + ballB.radius;
    var overlap:Number=sumRadius-Math.abs(xA-xB);//计算重叠部分
    //trace(overlap);
    	
    //计算每个球所占重叠部分中的比例
    var aRadio:Number = ballA.radius/sumRadius;
    var bRadio:Number = ballB.radius/sumRadius;
    	
    //分离判断
    if (overlap>0){
    	if (xA>xB){
    		xA += overlap*aRadio;
    		xB -= overlap*bRadio;
    	}
    	else{
    		xA -= overlap*aRadio;
    		xB += overlap*bRadio;
    	}
    }
    

    最后老规矩:来一个群魔乱舞,把一堆球放在一块儿乱撞

    package {
    
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.geom.Point;
    
    	public class MultiBilliard extends Sprite {
    
    		private var balls:Array;
    		private var numBalls:uint=8;
    		private var bounce:Number=-1.0;
    
    		public function MultiBilliard() {
    			init();
    		}
    
    		private function init():void {
    			balls = new Array();
    			for (var i:uint = 0; i < numBalls; i++) {
    				var radius:Number=Math.random()*40+10;
    				var ball:Ball=new Ball(radius,Math.random()*0xffffff);
    				ball.mass=radius;
    				ball.x=i*100;
    				ball.y=i*50;
    				ball.vx=Math.random()*10-5;
    				ball.vy=Math.random()*10-5;
    				addChild(ball);
    				balls.push(ball);
    			}
    			addEventListener(Event.ENTER_FRAME, onEnterFrame);
    		}
    
    		private function onEnterFrame(event:Event):void {
    			for (var i:uint = 0; i < numBalls; i++) {
    				var ball:Ball=balls[i];
    				ball.x+=ball.vx;
    				ball.y+=ball.vy;
    				checkWalls(ball);
    			}
    
    			for (i = 0; i < numBalls - 1; i++) {
    				var ballA:Ball=balls[i];
    				for (var j:Number = i + 1; j < numBalls; j++) {
    					var ballB:Ball=balls[j];
    					checkCollision(ballA, ballB);
    				}
    			}
    		}
    		
    
    		//舞台边界检测
    		function checkWalls(b:Ball) {
    			if (b.x<b.radius) {
    				b.x=b.radius;
    				b.vx*=bounce;
    			} else if (b.x>stage.stageWidth-b.radius) {
    				b.x=stage.stageWidth-b.radius;
    				b.vx*=bounce;
    			}
    			if (b.y<b.radius) {
    				b.y=b.radius;
    				b.vy*=bounce;
    			} else if (b.y>stage.stageHeight-b.radius) {
    				b.y=stage.stageHeight-b.radius;
    				b.vy*=bounce;
    			}
    		}
    
    		private function rotate(x:Number, y:Number, sin:Number, cos:Number, reverse:Boolean):Point {
    			var result:Point = new Point();
    			if (reverse) {
    				result.x=x*cos+y*sin;
    				result.y=y*cos-x*sin;
    			} else {
    				result.x=x*cos-y*sin;
    				result.y=y*cos+x*sin;
    			}
    			return result;
    		}
    
    		private function checkCollision(ball0:Ball, ball1:Ball):void {
    			var dx:Number=ball1.x-ball0.x;
    			var dy:Number=ball1.y-ball0.y;
    			var dist:Number=Math.sqrt(dx*dx+dy*dy);
    			if (dist<ball0.radius+ball1.radius) {
    				// 计算角度和正余弦值 
    				var angle:Number=Math.atan2(dy,dx);
    				var sin:Number=Math.sin(angle);
    				var cos:Number=Math.cos(angle);
    				// 旋转 ball0 的位置 
    				var pos0:Point=new Point(0,0);
    				// 旋转 ball1 的速度 
    				var pos1:Point=rotate(dx,dy,sin,cos,true);
    				// 旋转 ball0 的速度 
    				var vel0:Point=rotate(ball0.vx,ball0.vy,sin,cos,true);
    				// 旋转 ball1 的速度 
    				var vel1:Point=rotate(ball1.vx,ball1.vy,sin,cos,true);
    				// 碰撞的作用力 
    				var vxTotal:Number=vel0.x-vel1.x;
    				vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) / (ball0.mass + ball1.mass);
    				vel1.x = vxTotal+vel0.x;
    				// 更新位置 
    				var absV:Number=Math.abs(vel0.x)+Math.abs(vel1.x);
    				var overlap:Number = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x);
    				pos0.x += vel0.x/absV*overlap;
    				pos1.x += vel1.x/absV*overlap;
    				// 将位置旋转回来 
    				var pos0F:Object=rotate(pos0.x,pos0.y,sin,cos,false);
    				var pos1F:Object=rotate(pos1.x,pos1.y,sin,cos,false);
    				// 将位置调整为屏幕的实际位置 
    				ball1.x=ball0.x+pos1F.x;
    				ball1.y=ball0.y+pos1F.y;
    				ball0.x=ball0.x+pos0F.x;
    				ball0.y=ball0.y+pos0F.y;
    				// 将速度旋转回来 
    				var vel0F:Object=rotate(vel0.x,vel0.y,sin,cos,false);
    				var vel1F:Object=rotate(vel1.x,vel1.y,sin,cos,false);
    				ball0.vx=vel0F.x;
    				ball0.vy=vel0F.y;
    				ball1.vx=vel1F.x;
    				ball1.vy=vel1F.y;
    			}
    		}
    	}
    }
    

    注:这段代码做了优化,把一些公用的部分提取出来封装成function了,同时对于粘连问题的解决,采用了更一种算法

    后记:弄懂了本文中的这些玩意儿有啥用呢?让我想想,或许...公司需要开发一款桌面台球游戏时,这东西就能派上用场吧.

    作者:菩提树下的杨过
    出处:http://yjmyzz.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    二叉树的构造与遍历
    最长公共子序列
    Python爬虫与数据图表的实现
    降维实例之主成分分析
    数据集之转换器以及估计器
    机器学习算法分类以及开发流程
    数据的降维之特征选择及主成分分析
    特征工程之归一化及标准化
    文本tfidf
    文本特征抽取
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/1717896.html
Copyright © 2020-2023  润新知