Nape柔体教程(4)

我必须承认,上一节的柔体教程里,我犯了一个错误,在创建关节时,我把它们添加到了名为napeWorld的Space对象中去了。

		outJoint.space = napeWorld;
		innerJoint.space = napeWorld;

而实际应该添加到名为softBody的compound对象中。因为所有的刚体都属于这个softBOdy,作为柔体的一部分,关节自然也应该是属于softBody的。

		outJoint.compound = softBody;
		innerJoint.compound = softBody;

另外,关节连接的两个刚体依然发生碰撞检测,导致偶尔会发生刚体抖动现象。因此我们最好去掉关节刚体之间的碰撞检测。

		innerJoint.ignore = true;

但是做出上面两个部分的改动之后,你会发现,柔体变的非常柔了,就像一个没有充气的气球。

[swfobject]1012[/swfobject]

为什么这样子呢?在第2节柔体中我曾讲过,组成柔体的片段需要添加一个作用力,把这些片段刚体支撑起来,也就是第2节里讲的辐条。而在第3节中,支撑柔体的作用力分为两部分。

  1. 刚体之间的碰撞检测,和修复碰撞重叠部分时的作用力。
  2. 关节通过设置damping和frequency实现的弹簧拉力。

其中碰撞检测部分形成的支撑力占大部分,所以,我们设置joint.ignore为true时,柔体就一下子瘫下来了。

下面我们要做的就是找一种支撑力来解决方案。我的思路或者说Luca教我们的是,模拟大气对气球由内向外的张力。如下图所示。

 force_domestration

那么我们要做的就是针对每个刚体片段施加一个向外的作用力。这个方案的重点也是难点是,计算出作用力的方向。以为柔体的形状是不固定的,所以单纯的用三角函数计算刚体位置对应的离心方向,是无法满足需求的。具体的方法是这样的,注意听,有点复杂哦。

1. 实时计算刚体片段外面的边

倘若让我们自己去计算这个边的角度肯定是件很痛苦的事,好在Nape帮我们解决了这个问题。我们知道刚体的形状出了圆形就是Polygon,这里的每个刚体片段都是Polygon,Polygon类有一个edges属性,保存了多边形的所有边,然后我们可以通过edges.at()方法获取特定的边。

上一节我已经讲过刚体片段的顶点顺序,如下图所示。所以最外面的边的索引值是0,那么我们可以通过下面的代码获取外面的这条边。

		poly.edges.at(0);

2. 刚体外边的垂直方向即为作用力方向

知道了最外面那条边,它的垂直方向就简单多了。上一步我们得到的边是一个Edge对象,它还有一个方法叫做worldNormal的属性,用来计算边界在全局坐标系统下的垂直方向。知道了边界垂直方向,再乘以施加的作用力,然后传入到ApplyImpulse()方法里就可以了,代码如下:

		segment.applyImpulse(edge.worldNormal.mul(force,true),segment.position,false);

讲了这么多,还是看看示例吧!在下面的效果中,按下向上箭头给柔体”充气”,按下向下箭头给柔体”放气儿”。右上角可以看到Force的大小。

[swfobject]1014[/swfobject]

完整的代码如下,主要内容上面都见过了,我就不再注意解释了。

package learnNape {
	import com.bit101.components.Label;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import nape.shape.Edge;
	import nape.phys.Compound;
	import flash.events.Event;
	import nape.shape.Polygon;
	import nape.constraint.PivotJoint;
	import nape.phys.BodyList;
	import nape.geom.Vec2;
	import nape.phys.Body;
	import learnNape.AbstractNapeTest;

	/**
	 * @author yangfei
	 */
	public class T31_SoftBody2_2 extends AbstractNapeTest {

		public function T31_SoftBody2_2(gravity : Number = 600) {

		}
		private var softBody:Compound;
		private var force:Number=0;

		private var label:Label;

		override protected function onNapeWorldReady() : void {
			softBody = createSoftBody(200,100,100,40,10);

			label = new Label(this,480,20,"[Force : 0]");
		}

		private function createSoftBody(cx:Number,cy:Number,radius:Number,segmentsNum:int,thickness:Number) : Compound {
			var softBody:Compound = new Compound();
			var segmentList:BodyList = new BodyList();

			var angleGap:Number=Math.PI*2/segmentsNum;
			var innerRadius :Number = radius-thickness;

			var outPoints:Vector.<Vec2>= new Vector.<Vec2>();
			var innerPoints:Vector.<Vec2>= new Vector.<Vec2>();

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

			for (var i:int = 0; i< segmentsNum; i++){

				var angle1:Number = angleGap * i;
				var angle2:Number = angleGap * (i+1);
				var outP1:Vec2 = new Vec2(Math.sin(angle1)*radius+cx, Math.cos(angle1)*radius+cy);
				var outP2:Vec2 = new Vec2(Math.sin(angle2)*radius+cx, Math.cos(angle2)*radius+cy);
				var innerP1:Vec2 = new Vec2(Math.sin(angle1)*innerRadius+cx, Math.cos(angle1)*innerRadius+cy);
				var innerP2:Vec2 = new Vec2(Math.sin(angle2)*innerRadius+cx, Math.cos(angle2)*innerRadius+cy);

				var segment:Body = new Body();
				var poly:Polygon = new Polygon([outP1,outP2,innerP2,innerP1]);
				segment.shapes.add(poly);
				segment.align();
				segment.compound=softBody;

				outPoints.push(outP1);
				innerPoints.push(innerP1);
				segmentList.push(segment);
				outEdgeList.push(poly.edges.at(0));
			}
			var prevBody:Body, currBody:Body;
			var currentPoint:Vec2;

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

				currentPoint=innerPoints[j];
				var innerJoint : PivotJoint = new PivotJoint(prevBody, 
															currBody, 
															prevBody.worldPointToLocal(currentPoint), 
															currBody.worldPointToLocal(currentPoint));
				innerJoint.stiff = false;
				innerJoint.damping = 1;
				innerJoint.frequency = 5;
				innerJoint.compound = softBody;
				innerJoint.ignore = true;
			}

			softBody.space = napeWorld;
			softBody.userData.edges = outEdgeList;
			return softBody;
		}
		private function gasUp():void{
			var edges:Vector.<Edge> = softBody.userData.edges;

			for(var j:int;j< edges.length;j++){
				var edge:Edge = edges[j];
				var segment:Body = edge.polygon.body;

				segment.applyImpulse(edge.worldNormal.mul(force,true),segment.position,false);
			}
		}

		override protected function loop(event : Event) : void {
			gasUp();
			super.loop(event);
		}

		override protected function keyBoardEventHanlder(event : KeyboardEvent) : void {
			if(event.type == KeyboardEvent.KEY_DOWN){
				if(event.keyCode == Keyboard.UP){
					if(force<20){
						force+=1;
					}
				}else if(event.keyCode == Keyboard.DOWN){
					if(force>0){
						force-=1;
					}
				}
			}
			label.text = "[Force: " + String(force) + "]";

		}

	}
}

点击下载源文件

联系作者

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

4 Replies to “Nape柔体教程(4)”

  1. 谢谢这么好的教程!受启发,发现只要将 80行 var innerJoint : PivotJoint = new PivotJoint 变成 var innerJoint : WeldJoint = new WeldJoint( 就可以不加 gassUp这个步骤了,效果感觉一样哦。。。就是将里面的 关节变成 WeldJoint

  2. 是的,因为weldJoint本身会保持两个刚体的相对位置不变,所有多个weldJoint连接起来,会尽量保持原有的形状,进而实现了类似柔体的效果

  3. 是呀,因为发现gassUp的方法,如果重力为零的话,就是没有外力影响的话,会随某个方向移动~~~~可能是"充气"不均匀吧

发表回复

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