Nape柔体教程(5)

还记得我们学习的柔体创建的步骤吗?

一、用多个segment刚体组成柔体形状的轮廓,并用PivotJoint链接起来。

  1. 柔体的轮廓完全由segment刚体的形状组成,而非PivotJoint关节。
  2. 两个相邻segmentBody刚体之间用两个PivotJoint关节连接。

多边形柔体的创建过程基本上是一样的,不同之处在于步骤1.a,segment刚体的计算方法。

圆形是一个标准的正圆,它的轮廓通过三角函数和内外径可以轻松计算出来,然后分割成多个顶点,组成一个个小的segment刚体。但是多边形大多是不规则的形状,没有一个万能的公式来计算出这些形状的轮廓,所以我们要在这个问题上绕些弯子。

二、在刚体segment刚体的垂直方向施加作用力,把轮廓支撑起来。

  1. 获取刚体segment刚体外侧的边。
  2. 获取作用力方向。

这一点,我们在第4节柔体教程里也学习过了,没有太大的差异。不过第4节的作用力我是通过一个参数定义好之后(比如10),直接调用applyImpulse施加的,这一节我要教你另外一个方法。

首先我们快速浏览一下多边形柔体的创建方法。

  1. 获取多边形轮廓内外边界顶点,并计算出内外边界
  2. 针对内外边界进行等分
  3. 用等分边界生成的顶点创建segment刚体

softbody5-1

下面我们来仔细看一下具体每个步骤的实现过程。

获取内外边界顶点

对应多边形顶点的处理,Nape为我准备了一个专门的类GeomPoly(我在用GeomPoly创建多边形刚体中曾经讲过),我们可以将存储了顶点的Array<Vec2>, flash.Vector<Vec2>, Vec2List等类型传入到GeomPoly的构造函数中,实例化一个GeomPoly对象,然后调用相应的函数。

多边形的外边界顶点是很容易获取的,Polygon提供的静态方法box()、rect()和regular()方法都可以返回一个包含了多边形顶点的Vec2List对象,我们可以传递给GeometryPoly进行实例化。

outPoly = new GeomPoly( Polygon.box());

softbody5-2

那么怎么获取轮廓的内边界呢?同样还是求助于GeomPloy类了。GeomPoly有一个inflate()函数,用于对存储的多边形进行缩放。还记得Box2D缩放教程吗?inflate方法相对把多边形缩放方法单独放到了一个函数中。

inflate()的参数表示多边形缩放的尺寸,以像素为单位。整数表示多边形放大,负数表示缩小。然后将缩放后的多边形顶点存储到一个GeomPoly对象中返回。下面的代码将outPoly缩小10个像素并返回到innerPoly中去。

innerPoly = outPoly.inflate(-10);

softbody5-3

 

等分内外边界

等分边界之前,要知道等分后的segmentBody的长度,这一点并不难,我可以预先定义好保存到一个变量segmentLength中。

然后用要等分的边界长度outEdge.length除以segmentLength,计算出等分后segment的数量segmentNum,再循环遍历计算每个片段的顶点。

用等分边界生成的顶点创建segment

计算出每个顶点之后就简单多了,和圆形柔体一样,把这些顶点按照指定的顺序组合成一个刚体segment,然后逐个用PivotJoint关节连接起来,形成多边形柔体。

讲了这么多,估计你已经看烦了,还是先看看完成后的效果吧。在下面的示例中,点击右上角的GasON按钮,可以添加或消除柔体的支撑力。

[swfobject]1032[/swfobject]

代码如下:

package learnNape {
	import com.bit101.components.PushButton;
	import flash.events.Event;
	import nape.shape.Edge;
	import nape.geom.GeomPoly;
	import nape.phys.Compound;
	import nape.shape.Polygon;
	import nape.constraint.PivotJoint;
	import nape.geom.Vec2;
	import nape.phys.Body;
	import learnNape.AbstractNapeTest;

	/**
	 * @author yangfei
	 */
	public class T32_SoftBody3 extends AbstractNapeTest {
		public function T32_SoftBody3(gravity : Number = 600) {

		}

		private var softBodyList : Vector.<Compound> = new Vector.<Compound>();
		private var segmentLength:Number = 20;

		private var isGasOn:Boolean = false;

		override protected function onNapeWorldReady() : void {
			createSoftBody(200,100,10,new GeomPoly(Polygon.box(90, 90)));
			createSoftBody(300,100,10,new GeomPoly(Polygon.regular(60, 60, 5)));
			addJuggleButton();
		}

		private function addJuggleButton() : void {
			var btn:PushButton = new PushButton(this,400,20,"GasOFF", function(){
				if(btn.label=="GasON"){
					btn.label="GasOFF";
					isGasOn = false;
				}else{
					btn.label="GasON";
					isGasOn = true;
				}
			});
		}

		private function createSoftBody(cx:Number,cy:Number,thickness:Number, poly:GeomPoly) : void {
			var softBody:Compound = new Compound();
			var segmentList:Vector.<Body> = new Vector.<Body>();

			var segmentNum:int;

			var outPoly:GeomPoly = poly;
			var outPoints:Vector.<Vec2>= new Vector.<Vec2>();			
			var innerPoly:GeomPoly = poly.inflate(-thickness);
			var innerPoints:Vector.<Vec2>= new Vector.<Vec2>();

			var outSegmentEdgeList : Vector.<Edge> = new Vector.<Edge>();

			var outStart:Vec2 = outPoly.current();

			do{
				var outCurrent:Vec2 = outPoly.current();
				outPoly.skipForward(1);
				var outNext:Vec2 = outPoly.current();
				var outEdge:Vec2 = outNext.sub(outCurrent);

				var innerCurrent:Vec2 = innerPoly.current();
				innerPoly.skipForward(1);
				var innerNext:Vec2=innerPoly.current();
				var innerEdge:Vec2 = innerNext.sub(innerCurrent);

				segmentNum = Math.ceil(outEdge.length/segmentLength);

				for (var i:int=0; i<segmentNum; i++){
					var outSegmentCurr:Vec2 = outCurrent.addMul(outEdge, i/segmentNum);
					var outSegmentNext:Vec2 = outCurrent.addMul(outEdge, (i+1)/segmentNum);
					var innerSegmentCurr:Vec2 = innerCurrent.addMul(innerEdge, i/segmentNum);
					var innerSegmentNext:Vec2 = innerCurrent.addMul(innerEdge, (i+1)/segmentNum);

					var segment:Body = new Body();
					var shape:Polygon = new Polygon([outSegmentCurr,outSegmentNext,innerSegmentNext,innerSegmentCurr]);
					segment.shapes.add(shape);
					segment.align();
					segment.compound=softBody;

					outPoints.push(outSegmentCurr);
					innerPoints.push(innerSegmentCurr);
					segmentList.push(segment);

					outSegmentEdgeList.push(shape.edges.at(0));
				}
				innerEdge.dispose();
				outEdge.dispose();

			}while(outPoly.current()!=outStart);

			var prevBody:Body, currBody:Body;
			var currentPoint:Vec2;

			for (var j:int=0;j< segmentList.length; j++){
				prevBody=segmentList[(j-1+segmentList.length)%segmentList.length];
				currBody=segmentList[j];
				currentPoint=outPoints[j];
				var outJoint : PivotJoint = new PivotJoint(prevBody,
															currBody, 
															prevBody.worldPointToLocal(currentPoint,true), 
															currBody.worldPointToLocal(currentPoint,true));
				outJoint.compound = softBody;

				currentPoint=innerPoints[j];
				var innerJoint : PivotJoint = new PivotJoint(prevBody, 
															currBody, 
															prevBody.worldPointToLocal(currentPoint,true), 
															currBody.worldPointToLocal(currentPoint,true));
				innerJoint.stiff = false;
				innerJoint.damping = 1;
				innerJoint.frequency = 10;
				innerJoint.compound = softBody;
				innerJoint.ignore = true;
			}
			for (i = 0; i < segmentList.length; i++) {
                segmentList[i].position.addeq(Vec2.weak(cx,cy));
            }
			softBody.userData.area = outPoly.area();
			softBody.userData.outSegmentEdgeList = outSegmentEdgeList;

			softBody.space = napeWorld;
			softBodyList.push(softBody);
		}
		private function gasUp():void{
			var sBody: Compound;
			var pressure : Number;

			for each(sBody in softBodyList){
				pressure = (sBody.userData.area - getArea(sBody))/60;
				var edges : Vector.<Edge> = sBody.userData.outSegmentEdgeList;
				for(var i:int = 0; i< edges.length;i++){
					var e:Edge = edges[i];
					var b:Body = e.polygon.body;
					b.applyImpulse(e.worldNormal.mul(pressure,true),b.position,true);
				}
			}
		}

		override protected function loop(event : Event) : void {
			if(isGasOn) gasUp();
			super.loop(event);
		}
		private function getArea(s:Compound):Number{
			var edges : Vector.<Edge> = s.userData.outSegmentEdgeList;
			var areaPoly : GeomPoly = new GeomPoly();
			var area:Number;

			for each(var e:Edge in edges){
				areaPoly.push(e.worldVertex1);
			}
			area= areaPoly.area();
			areaPoly.dispose();

			return area;
		}
	}
}

第21行:每个软体由多个简单的刚体组成,这些刚体由一个Compound对象统一管理,你可以把他想象成 一个包含多个Sprite的Sprite容器。所以,用来保存柔体的数组是 Vector.<Compound>类型的。
第22行:segmentLength定义了每个刚体片段的长度
第24行:isGasOn表示是否对柔体施加由内向外的支撑力。
第26~42行:定义好全局变量之后,因为示例继承了AbstractNapeTest类,所以代Nape世界创建好之后,会自动调用 onNapeWorldReady()函数,我们在这个函数里创两个软体,一个矩形、另一个是5边形。然后调用 addJuggleButton()函数添加一个开关按钮。
第44~128行:
这个createSoftBody函数是本节的重点。调用createSoftBody后会创建一个柔体对象,并返回,在返回
之前,我把他存储到45行定义的softBody对象中。
我说过需要把多边形的边分解成多个片段,我们把这些片段保存到segmentList数组中。
实际上,每条边都是要根据前面定义的segmentLength进行等分的,等分后的数量保存到segmentNum变量
中。
第51~53行:分别用outPoly和innerPoly保存了多边形的内外轮廓,这里用到了前面介绍的
GeomPoly.inflate()方法,来获取内边界innerPoly。后面的代码会将内外边界的顶点分别保存到
innerPoints和outPoints数组中。
第55行:多边形分割成多个片段后,将这些片段的最外面的边存储到outSegmentEdgeList中,稍后计算
支撑力方向和多边形面积时会用到。
第57行:记录多边形的第一个顶点,后面我们会逐一遍历多边形的每个顶点,获取每条边进行分割。
第59~94行:这里的do while循环用来遍历每一条边,并对每条边进行分割,就像上图中的第2部。
第60~63行:首先调用outPoly.current()函数记录多边形的起始顶点,然后执行skipForward(1),将顶
点的索引移至下一个顶点,并保存到outNext变量中。然后用outNext减去outCurrent,我们就得到了多
边形的第一条外边界。
第65~68行:用上面同样的方法,计算多边形的内边界。
第70行:计算刚体片段的数量segmentNum。
第72~89行:这里的这个for循环,是循环将当前索引到的多边形边界,分割成多个小的片段,并保存到
segmentList中去。
第73~76行:计算分割后的刚体片段的4个顶点。
第78~82行:通过上一步计算的顶点创建刚体片段。
第84~85行:保存刚体片段的内外顶点,稍后创建PivotJoint关节时会用到。
第86行:将刚体片段保存到segmentList中去。
第88行:将刚体片段的最外面的边保存到outSegmentEdgeList中,稍后计算支撑力方向和面积时用到.
第90~91行:删除临时变量,节约内存
第99~119行:循环遍历segmentList里的刚体,用PivotJoint将它们连接起来。
第120~122行:调整每个刚体的坐标至柔体的坐标位置,因为创建初始,是基于坐标原点(0,0)的
第123~124行:保存柔体的面积,以及所有外边缘的引用,稍后就算支撑力时用到
第129~142行:循环遍历每个柔体,对每个柔体内的刚体施加由内向外的作用力。
第133行:第一层for循环遍历softBodyList中的每个柔体。
第134行:柔体变形后,他的面积会变小,我们可以理解成对气球的挤压,我们挤压的越用力,内部气体对气球的支撑力也越大。所以这里的pressure是用开始我们在第123行保存的面积,减去用getArea实时计算出来的面积,所谓作用力。
第136~139行:遍历第124行我们保存的每个外边缘,通过Edge.worldNormal()方法获取垂直该边缘的方向作为作用力的方法,然后用mul()方法乘以刚才计算出来的pressure,施加给刚体片段

第148~160行:用外边缘的顶点新建一个Polygon,然后用Polygon.area()实时计算柔体的面积

点击下面源代码

 

联系作者

公众号:拉小登 | 微博:拉登Dony | B站:拉小登Excel

One Reply to “Nape柔体教程(5)”

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注