11RIA 闪客社区 - 最赞 Animate Flash 论坛

搜索
查看: 1841|回复: 0
上一主题 下一主题

[2D 物理引擎] 【9RIA—ladeng6666】—【Box2D系列教程 篇外篇】切割Box2D对象(二)

[复制链接] TA的其它主题
发表于 2018-2-6 13:32:08 | 显示全部楼层 |阅读模式

【游客模式】——注册会员,加入11RIA 闪客社区吧!一起见证Flash的再次辉煌……

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

转载:9RIA游戏开发者社区(天地会)
作者:ladeng6666(拉登大叔)
作者博客:http://www.ladeng6666.com/blog/


【Box2D系列教程-导航帖】—拉登大叔出品(总贴)


这是用Box2D实现切割效果的第二部分了。在第一部分教程中,我们找到了切割的切入点和切出点,现在该实现真正的切割了。
切割对象,我们只需要三个步骤,马上开始吧!

首先找到激光的中心点
在这一步中,我们需要找到激光在对象内部的中心点。换句话说,如果我们认为切入点和起初点之间是激光在对象内部的部分,那么我们必须找到他们的中心点,这对我们下面实现切割过程有很大的帮助。
虽然要修改laserFired函数里面的内容,但这一点实现起来很简单

[Actionscript3] 纯文本查看 复制代码
package {
        import Box2D.Dynamics.*;
        import Box2D.Collision.*;
        import Box2D.Collision.Shapes.*;
        import Box2D.Common.Math.*;
        import flash.display.Sprite;
        import flash.events.MouseEvent;
        import flash.events.Event;
        public class Main extends Sprite {
                private var world:b2World=new b2World(new b2Vec2(0,10),true);
                private var worldScale:int=30;
                private var canvas:Sprite;
                private var laserSegment:b2Segment;
                private var drawing:Boolean=false;
                private var affectedByLaser:Vector.<b2Body>;
                private var entryPoint:Vector.<b2Vec2>;
                public function Main() {
                        debugDraw();
                        addStuff();
                        canvas = new Sprite();
                        addChild(canvas);
                        addEventListener(Event.ENTER_FRAME, updateWorld);
                        stage.addEventListener(MouseEvent.MOUSE_DOWN,mousePressed);
                        stage.addEventListener(MouseEvent.MOUSE_MOVE,mouseMoved);
                        stage.addEventListener(MouseEvent.MOUSE_UP,mouseReleased);
                }
                private function mousePressed(e:MouseEvent):void {
                        drawing=true;
                        laserSegment=new b2Segment();
                        laserSegment.p1=new b2Vec2(mouseX/worldScale,mouseY/worldScale);
                }
                private function mouseMoved(e:MouseEvent):void {
                        if (drawing) {
                                canvas.graphics.clear();
                                canvas.graphics.lineStyle(1,0xff0000);
                                canvas.graphics.moveTo(laserSegment.p1.x*worldScale,laserSegment.p1.y*worldScale);
                                canvas.graphics.lineTo(mouseX,mouseY);
                        }
                }
                private function mouseReleased(e:MouseEvent):void {
                        drawing=false;
                        laserSegment.p2=new b2Vec2(mouseX/worldScale,mouseY/worldScale);
                }
                private function debugDraw():void {
                        var debugDraw:b2DebugDraw = new b2DebugDraw();
                        var debugSprite:Sprite = new Sprite();
                        addChild(debugSprite);
                        debugDraw.SetSprite(debugSprite);
                        debugDraw.SetDrawScale(worldScale);
                        debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
                        debugDraw.SetFillAlpha(0.5);
                        world.SetDebugDraw(debugDraw);
                }
                private function addStuff():void {
                        var floorBody:b2BodyDef= new b2BodyDef();
                        floorBody.position.Set(11,16);
                        floorBody.userData=new Object();
                        var floorShape:b2PolygonShape = new b2PolygonShape();
                        floorShape.SetAsBox(15,0.5);
                        var floorFixture:b2FixtureDef = new b2FixtureDef();
                        floorFixture.shape=floorShape;
                        var worldFloor:b2Body=world.CreateBody(floorBody);
                        worldFloor.CreateFixture(floorFixture);
                        //
                        var squareBody:b2BodyDef= new b2BodyDef();
                        squareBody.position.Set(16,5);
                        var squareShape:b2PolygonShape = new b2PolygonShape();
                        squareShape.SetAsBox(2.5,2.5);
                        var squareFixture:b2FixtureDef = new b2FixtureDef();
                        squareFixture.shape=squareShape;
                        var worldSquare:b2Body=world.CreateBody(squareBody);
                        worldSquare.CreateFixture(squareFixture);
                        //
                        var circleVector:Vector.<b2Vec2>=new Vector.<b2Vec2>();
                        var circleSteps:int=12;
                        var circleRadius:Number=3;
                        for (var i:int=0; i<circleSteps; i++) {
                                circleVector.push(new b2Vec2(circleRadius*Math.cos(2*Math.PI/circleSteps*i),circleRadius*Math.sin(2*Math.PI/circleSteps*i)));
                        }
                        var circleBody:b2BodyDef= new b2BodyDef();
                        circleBody.position.Set(5,5);
                        var circleShape:b2PolygonShape = new b2PolygonShape();
                        circleShape.SetAsVector(circleVector,circleSteps);
                        var circleFixture:b2FixtureDef = new b2FixtureDef();
                        circleFixture.shape=circleShape;
                        var worldCircle:b2Body=world.CreateBody(circleBody);
                        worldCircle.CreateFixture(circleFixture);
                }
                private function updateWorld(e:Event):void {
                        world.Step(1/30,10,10);
                        world.ClearForces();
                        if (laserSegment&&! drawing) {
                                affectedByLaser=new Vector.<b2Body>();
                                entryPoint=new Vector.<b2Vec2>();
                                world.RayCast(laserFired,laserSegment.p1,laserSegment.p2);
                                world.RayCast(laserFired,laserSegment.p2,laserSegment.p1);
                                laserSegment=null;
                        }
                        world.DrawDebugData();
                }
                private function laserFired(fixture:b2Fixture,point:b2Vec2,normal:b2Vec2,fraction:Number):Number {
                        var affectedBody:b2Body=fixture.GetBody()
                        var fixtureIndex:int=affectedByLaser.indexOf(affectedBody)
                        if (fixtureIndex==-1) {
                                affectedByLaser.push(affectedBody);
                                entryPoint.push(point);
                        } else {
                                drawCircle(entryPoint[fixtureIndex],0xff0000);
                                drawCircle(point,0xffff00);
                                var rayCenter:b2Vec2=new b2Vec2((point.x+entryPoint[fixtureIndex].x)/2,(point.y+entryPoint[fixtureIndex].y)/2);
                                drawCircle(rayCenter,0xff8800);
                        }
                        return 1;
                }
                private function drawCircle(origin:b2Vec2,color:Number):void {
                        canvas.graphics.lineStyle(2,color);
                        canvas.graphics.drawCircle(origin.x*worldScale,origin.y*worldScale,5);
                }
        }
}


首先,添加两个新的变量:
    15行:affectedByLaser,一个b2Body数组,用来存储被激光切到的刚体。
    16行:entryPoint,一个b2Vec2数组,用来存储激光的切入点。

下面我们来仔细看一下laserFired方法(第101~114行)。
现在要做的不单单只是画个圆的事情了,我们需要区分切入点和切出点,用不同的效果来绘制这两个点会对我们有所帮助,持续追踪被影响到的刚体,并找到它内部激光的中心点。
    102行:创建一个名为affectedBody的b2Body变量,它表示我们再getBody方法里创建的,切被激光切割的刚体。
    103行:创建一个名为fiextureIndex的变量,使用indexOf可以得到affectedBody是否包含在affectedByLaser数组中。
    104行:如果fixtureIndex等于-1,表示affectedBody不在affectedByLaser数组中,所以我们可以说,它不是激光切割到的第一个刚体,很明显,我们必须处理切入点。
    105~106行:将被切割的刚体和交互点分别存储到affectedByLaser和entryPoint数组中。
    107行:如果fixtureIndex不是-1呢?这个表面它不是第一个affectedBody,这对我们来说,包含两种含义:它是一个已存在的切出点,它是被激光切割的刚体。
    108行:绘制切入点。
    109行:绘制切出点。
    110行:切入点和切出点的中心点。
    111行:绘制中心点
结果如下:



试着用激光切割对象。
现在刚体被激光完全切割开来,这个激光包含切入点、切出点和中心点。其他情况下,激光与刚体只有一个接触点时,我们认为它没有被切割。
切入点标示为红色、切出点黄色、中心点是橙色。

定义新的多边形顶点
在将刚体切割成两部分之前,我们要知道这个刚体有那些顶点在第一部分中,那些顶点在第二部分中。
怎么实现呢?道理很简单:假设激光是一个坐标轴,那么所有在坐标轴之上的顶点属于第一部分,所有在坐标轴之下的顶点属于第二部分,每个部分都包含切入点和切出点。
laserFired方法更新如下:

[Actionscript3] 纯文本查看 复制代码
private function laserFired(fixture:b2Fixture,point:b2Vec2,normal:b2Vec2,fraction:Number):Number {
        var affectedBody:b2Body=fixture.GetBody();
        var affectedPolygon:b2PolygonShape=fixture.GetShape() as b2PolygonShape;
        var fixtureIndex:int=affectedByLaser.indexOf(affectedBody);
        if (fixtureIndex==-1) {
                affectedByLaser.push(affectedBody);
                entryPoint.push(point);
        } else {
                drawCircle(entryPoint[fixtureIndex],0xff0000);
                drawCircle(point,0xffff00);
                var rayCenter:b2Vec2=new b2Vec2((point.x+entryPoint[fixtureIndex].x)/2,(point.y+entryPoint[fixtureIndex].y)/2);
                var rayAngle:Number=Math.atan2(entryPoint[fixtureIndex].y-point.y,entryPoint[fixtureIndex].x-point.x);
                drawCircle(rayCenter,0xff8800);
                var polyVertices:Vector.<b2Vec2>=affectedPolygon.GetVertices();
                for (var i:int=0; i<polyVertices.length; i++) {
                        var worldPoint:b2Vec2=affectedBody.GetWorldPoint(polyVertices[i]);
                        var cutAngle:Number=Math.atan2(worldPoint.y-rayCenter.y,worldPoint.x-rayCenter.x)-rayAngle;
                        if (cutAngle<Math.PI*-1) {
                                cutAngle+=2*Math.PI;
                        }
                        if (cutAngle>0&&cutAngle<=Math.PI) {
                                drawCircle(worldPoint,0xffffff);
                        } else {
                                drawCircle(worldPoint,0x000000);
                        }
                }
        }
        return 1;
}


    112行:rayAngle表示激光切割的角度
    114行:polyVertices数组存储多边形的所有顶点,这些顶点由GetVertices()方法返回。
    115行:遍历所有的顶点,并判断它们分别属于第一部分还是第二部分。
    116行:worldPoint(b2Vec2变量)获得顶点的坐标,该坐标由GetWorldPoint方法返回。
    117行:代码的核心部分:cutAngle是顶点以激光为坐标轴的相对角度,这个坐标轴可以由切入点和中心点组成。
    121~125行:每个顶点根据它的角度分别被标示成了黑色或白色的圆圈。

现在,我们可以看到切割后产生的新顶点了:



试着切割对象,你会看到切割后的顶点被标示成了黑色和白色。
最后,切割对象!

切割对象
虽然这是最后一步了,但这一步不是最简单,甚至可以说是最难的。我们要管理被切割后的对象。
首先是一些理论。正如你在前面我们完成的效果里看到的,我们可以很轻松的区分出切割后的顶点:一个由白色的顶点和切入点、切出点组成,另一个由黑色顶点和切入点、切出点组成。
代码需要再复杂一点,因为Box2D想让多边形的顶点按顺时针方向排列。很多网站和论坛会告诉你的逆时针方向是错误的,至少对于Box2D和Flash是错误的。
所以算法应该是:白色顶点顺时针排列,再加上切入点和切出点组成一部分,另外一部分由黑色顶点顺时针排列,再加上切入点和切出点组成。注意,在白色和黑色顶点里的切入点和切出点的顺序是反向的。
下面是完整的代码:

[Actionscript3] 纯文本查看 复制代码
package {
        import Box2D.Dynamics.*;
        import Box2D.Collision.*;
        import Box2D.Collision.Shapes.*;
        import Box2D.Common.Math.*;
        import flash.display.Sprite;
        import flash.events.MouseEvent;
        import flash.events.Event;
        public class Main extends Sprite {
                private var world:b2World=new b2World(new b2Vec2(0,10),true);
                private var worldScale:int=30;
                private var canvas:Sprite;
                private var laserSegment:b2Segment;
                private var drawing:Boolean=false;
                private var affectedByLaser:Vector.<b2Body>;
                private var entryPoint:Vector.<b2Vec2>;
                public function Main() {
                        debugDraw();
                        addStuff();
                        canvas = new Sprite();
                        addChild(canvas);
                        addEventListener(Event.ENTER_FRAME, updateWorld);
                        stage.addEventListener(MouseEvent.MOUSE_DOWN,mousePressed);
                        stage.addEventListener(MouseEvent.MOUSE_MOVE,mouseMoved);
                        stage.addEventListener(MouseEvent.MOUSE_UP,mouseReleased);
                }
                private function mousePressed(e:MouseEvent):void {
                        drawing=true;
                        laserSegment=new b2Segment();
                        laserSegment.p1=new b2Vec2(mouseX/worldScale,mouseY/worldScale);
                }
                private function mouseMoved(e:MouseEvent):void {
                        if (drawing) {
                                canvas.graphics.clear();
                                canvas.graphics.lineStyle(1,0xff0000);
                                canvas.graphics.moveTo(laserSegment.p1.x*worldScale,laserSegment.p1.y*worldScale);
                                canvas.graphics.lineTo(mouseX,mouseY);
                        }
                }
                private function mouseReleased(e:MouseEvent):void {
                        drawing=false;
                        laserSegment.p2=new b2Vec2(mouseX/worldScale,mouseY/worldScale);
                }
                private function debugDraw():void {
                        var debugDraw:b2DebugDraw = new b2DebugDraw();
                        var debugSprite:Sprite = new Sprite();
                        addChild(debugSprite);
                        debugDraw.SetSprite(debugSprite);
                        debugDraw.SetDrawScale(worldScale);
                        debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
                        debugDraw.SetFillAlpha(0.5);
                        world.SetDebugDraw(debugDraw);
                }
                private function addStuff():void {
                        var floorBody:b2BodyDef= new b2BodyDef();
                        floorBody.position.Set(11,16);
                        floorBody.userData=new Object();
                        var floorShape:b2PolygonShape = new b2PolygonShape();
                        floorShape.SetAsBox(15,0.5);
                        var floorFixture:b2FixtureDef = new b2FixtureDef();
                        floorFixture.shape=floorShape;
                        var worldFloor:b2Body=world.CreateBody(floorBody);
                        worldFloor.CreateFixture(floorFixture);
                        //
                        var squareBody:b2BodyDef= new b2BodyDef();
                        squareBody.position.Set(16,5);
                        var squareShape:b2PolygonShape = new b2PolygonShape();
                        squareShape.SetAsBox(2.5,2.5);
                        var squareFixture:b2FixtureDef = new b2FixtureDef();
                        squareFixture.shape=squareShape;
                        var worldSquare:b2Body=world.CreateBody(squareBody);
                        worldSquare.CreateFixture(squareFixture);
                        //
                        var circleVector:Vector.<b2Vec2>=new Vector.<b2Vec2>();
                        var circleSteps:int=12;
                        var circleRadius:Number=3;
                        for (var i:int=0; i<circleSteps; i++) {
                                circleVector.push(new b2Vec2(circleRadius*Math.cos(2*Math.PI/circleSteps*i),circleRadius*Math.sin(2*Math.PI/circleSteps*i)));
                        }
                        var circleBody:b2BodyDef= new b2BodyDef();
                        circleBody.position.Set(5,5);
                        var circleShape:b2PolygonShape = new b2PolygonShape();
                        circleShape.SetAsVector(circleVector,circleSteps);
                        var circleFixture:b2FixtureDef = new b2FixtureDef();
                        circleFixture.shape=circleShape;
                        var worldCircle:b2Body=world.CreateBody(circleBody);
                        worldCircle.CreateFixture(circleFixture);
                }
                private function updateWorld(e:Event):void {
                        world.Step(1/30,10,10);
                        world.ClearForces();
                        if (laserSegment&&! drawing) {
                                affectedByLaser=new Vector.<b2Body>();
                                entryPoint=new Vector.<b2Vec2>();
                                world.RayCast(laserFired,laserSegment.p1,laserSegment.p2);
                                world.RayCast(laserFired,laserSegment.p2,laserSegment.p1);
                                laserSegment=null;
                        }
                        world.DrawDebugData();
                }
                private function laserFired(fixture:b2Fixture,point:b2Vec2,normal:b2Vec2,fraction:Number):Number {
                        var affectedBody:b2Body=fixture.GetBody();
                        var affectedPolygon:b2PolygonShape=fixture.GetShape() as b2PolygonShape;
                        var fixtureIndex:int=affectedByLaser.indexOf(affectedBody);
                        if (fixtureIndex==-1) {
                                affectedByLaser.push(affectedBody);
                                entryPoint.push(point);
                        } else {
                                var rayCenter:b2Vec2=new b2Vec2((point.x+entryPoint[fixtureIndex].x)/2,(point.y+entryPoint[fixtureIndex].y)/2);
                                var rayAngle:Number=Math.atan2(entryPoint[fixtureIndex].y-point.y,entryPoint[fixtureIndex].x-point.x);
                                var polyVertices:Vector.<b2Vec2>=affectedPolygon.GetVertices();
                                var newPolyVertices1:Vector.<b2Vec2>=new Vector.<b2Vec2>();
                                var newPolyVertices2:Vector.<b2Vec2>=new Vector.<b2Vec2>();
                                var currentPoly:int=0;
                                var cutPlaced1:Boolean=false;
                                var cutPlaced2:Boolean=false;
                                for (var i:int=0; i<polyVertices.length; i++) {
                                        var worldPoint:b2Vec2=affectedBody.GetWorldPoint(polyVertices[i]);
                                        var cutAngle:Number=Math.atan2(worldPoint.y-rayCenter.y,worldPoint.x-rayCenter.x)-rayAngle;
                                        if (cutAngle<Math.PI*-1) {
                                                cutAngle+=2*Math.PI;
                                        }
                                        if (cutAngle>0&&cutAngle<=Math.PI) {
                                                if (currentPoly==2) {
                                                        cutPlaced1=true;
                                                        newPolyVertices1.push(point);
                                                        newPolyVertices1.push(entryPoint[fixtureIndex]);
                                                }
                                                newPolyVertices1.push(worldPoint);
                                                currentPoly=1;
                                        } else {
                                                if (currentPoly==1) {
                                                        cutPlaced2=true;
                                                        newPolyVertices2.push(entryPoint[fixtureIndex]);
                                                        newPolyVertices2.push(point);
                                                }
                                                newPolyVertices2.push(worldPoint);
                                                currentPoly=2;

                                        }
                                }
                                if (! cutPlaced1) {
                                        newPolyVertices1.push(point);
                                        newPolyVertices1.push(entryPoint[fixtureIndex]);
                                }
                                if (! cutPlaced2) {
                                        newPolyVertices2.push(entryPoint[fixtureIndex]);
                                        newPolyVertices2.push(point);
                                }
                                createSlice(newPolyVertices1,newPolyVertices1.length);
                                createSlice(newPolyVertices2,newPolyVertices2.length);
                                world.DestroyBody(affectedBody);
                        }
                        return 1;
                }
                private function findCentroid(vs:Vector.<b2Vec2>, count:uint):b2Vec2 {
                        var c:b2Vec2 = new b2Vec2();
                        var area:Number=0.0;
                        var p1X:Number=0.0;
                        var p1Y:Number=0.0;
                        var inv3:Number=1.0/3.0;
                        for (var i:int = 0; i < count; ++i) {
                                var p2:b2Vec2=vs[i];
                                var p3:b2Vec2=i+1<count?vs[int(i+1)]:vs[0];
                                var e1X:Number=p2.x-p1X;
                                var e1Y:Number=p2.y-p1Y;
                                var e2X:Number=p3.x-p1X;
                                var e2Y:Number=p3.y-p1Y;
                                var D:Number = (e1X * e2Y - e1Y * e2X);
                                var triangleArea:Number=0.5*D;
                                area+=triangleArea;
                                c.x += triangleArea * inv3 * (p1X + p2.x + p3.x);
                                c.y += triangleArea * inv3 * (p1Y + p2.y + p3.y);
                        }
                        c.x*=1.0/area;
                        c.y*=1.0/area;
                        return c;
                }
                private function createSlice(vertices:Vector.<b2Vec2>,numVertices:int):void {
                        var centre:b2Vec2=findCentroid(vertices,vertices.length);
                        for (var i:int=0; i<numVertices; i++) {
                                vertices[i].Subtract(centre);
                        }
                        var sliceBody:b2BodyDef= new b2BodyDef();
                        sliceBody.position.Set(centre.x, centre.y);
                        sliceBody.type=b2Body.b2_dynamicBody;
                        var slicePoly:b2PolygonShape = new b2PolygonShape();
                        slicePoly.SetAsVector(vertices,numVertices);
                        var sliceFixture:b2FixtureDef = new b2FixtureDef();
                        sliceFixture.shape=slicePoly;
                        sliceFixture.density=1;
                        var worldSlice:b2Body=world.CreateBody(sliceBody);
                        worldSlice.CreateFixture(sliceFixture);
                        for (i=0; i<numVertices; i++) {
                                vertices[i].Add(centre);
                        }
                }
        }
}


和前面一样,laserFired方法的工作很艰难,将新生产的多边形顶点分别保存到newPolyVertices和newPolyVertices数组中,如112~113行。
    117~149行:创建这两个数组保存每一个部分的顶点,是一个”不太聪明”的做法,因为我用了很多没用的变量,所以你可以按照自己的想法修改并优化这部分代码。

不管怎么样,一旦我们创建好了存储顶点的数组,第150和151行会调用createSlice方法(稍后我会解释这个方法),最后删除DestroyBody方法(152行)里切割的刚体。
现在我们来说说createSlice方法,它包含两个参数:存储顶点的数组和这个数组的长度。我在写代码时,意识到这个方法需要两个参数,而且第二个参数可以从第一个参数中获得。
然后第180行的代码用来计算激光的中心点。findCentroid方法与protected类型的ComputeCentroid方法(你可以在b2PolygonShape.as文件中找到这个方法)一样。
    第181~183行:找到中心点后,需要将顶点的坐标系由Box2D中的绝对坐标系改为相对于中心点的相对坐标系。
    第184~193行:用数组中的定向创建刚体,这个动作由188行的SetAsVector方法完成。

最后,因为新生成的两个刚体共用了两个顶点(切入点和切出点),而且这个两个点都是以引用的形式传递给刚体的,所以我们必须将这两个相对值转换为绝对值(第194~196行)。
全文结束!!!



最后,你可以任意的切割对象了。!
但到这里还没有完全结束,现在我们要去掉调试用的图形,然后给对象添加一些皮肤。稍后我将会向你解释如何实现。最后,请下载本文的所有源代码

原文链接:http://www.emanueleferonato.com/2011/06/25/slicing-splitting-and-cutting-objects-with-box2d-part-2/
翻译词数:2707

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐 上一条 /1 下一条

感谢所有支持论坛的朋友:下面展示最新的5位赞助和充值的朋友……更多赞助和充值朋友的信息,请查看:永远的感谢名单

SGlW(66139)、 anghuo(841)、 whdsyes(255)、 longxia(60904)、 囫囵吞澡(58054)

下面展示总排行榜的前3名(T1-T3)和今年排行榜的前3名的朋友(C1-C3)……更多信息,请查看:总排行榜今年排行榜

T1. fhqu1462(969)、 T2. lwlpluto(14232)、 T3. 1367926921(962)  |  C1. anghuo(147)、 C2. fdisker(27945)、 C3. 囫囵吞澡(58054)



快速回复 返回顶部 返回列表