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节中,支撑柔体的作用力分为两部分。
- 刚体之间的碰撞检测,和修复碰撞重叠部分时的作用力。
- 关节通过设置damping和frequency实现的弹簧拉力。
其中碰撞检测部分形成的支撑力占大部分,所以,我们设置joint.ignore为true时,柔体就一下子瘫下来了。
下面我们要做的就是找一种支撑力来解决方案。我的思路或者说Luca教我们的是,模拟大气对气球由内向外的张力。如下图所示。
那么我们要做的就是针对每个刚体片段施加一个向外的作用力。这个方案的重点也是难点是,计算出作用力的方向。以为柔体的形状是不固定的,所以单纯的用三角函数计算刚体位置对应的离心方向,是无法满足需求的。具体的方法是这样的,注意听,有点复杂哦。
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) + "]"; } } }
联系作者
恩,这个效果不错,可以做气球,汽车的轮子,可以做好多好玩的
谢谢这么好的教程!受启发,发现只要将 80行 var innerJoint : PivotJoint = new PivotJoint 变成 var innerJoint : WeldJoint = new WeldJoint( 就可以不加 gassUp这个步骤了,效果感觉一样哦。。。就是将里面的 关节变成 WeldJoint
是的,因为weldJoint本身会保持两个刚体的相对位置不变,所有多个weldJoint连接起来,会尽量保持原有的形状,进而实现了类似柔体的效果
是呀,因为发现gassUp的方法,如果重力为零的话,就是没有外力影响的话,会随某个方向移动~~~~可能是"充气"不均匀吧