使用Box2d实现物体在液体中的漂浮效果(二)

原创 2017年01月03日 20:54:15


我们继续来制作物体在液体中的漂浮效果。

我们来考虑物体和液体的三种位置关系:

1.    物体完全离开液体

2.    物体一部分浸入液体

3.    物体完全浸入液体。

针对这三种位置关系,我们有下面三种结论:

1.      物体完全离开液体,物体与液体的表面没有交点,且ContactListener不检测物体与液体的接触,因此物体的UserData的isUnderWater属性为false,volumnUnderWater为0

2.      物体一部分浸入液体,物体与液体表面有交点,UserData的isUnderWater为true,volumnUnderWater大于0

3.      物体完全浸入液体,物体与液体表面没有交点,UserData的isUnderWater为true,volumnUnderWater为0

你可能会疑问为什么第三种情况中物体完全浸入液体时volumnUnderWater为0,这和我们的算法有关,由于想要精确计算物体浸入液体的体积,我们需要使用射线投射(RayCast)来检测物体与液体表面的交点,如下图:


我们使用穿过液体表面的射线对物体进行两个方向的投射,得到物体与液体的两个交点(如果没有交点,则说明液体完全浸入液体中或者完全离开液体),再利用得到的两个交点与物体在液体下方的顶点组成的多边形求解其浸入液体中的面积,因此如果没有交点,物体浸入液体的面积我们就认为是0。

应用上面的三种情况我们已经能够对物体状态进行区分了,由于物体只有浸入液体的时候才受到浮力,因此首先判断物体是否在液体中,如果在液体中,判断volumnUnderWater是否为0,如果为0,则通过物体的质量和密度的比值求出其整个的体积,不为0的话,volumnUnderWater就是其浸入液体中的体积。最后再根据浮力计算公式来求解其受到的浮力大小。

首先定义类MyRayCastCallback类,继承自b2RayCastCallback:

#import"Box2D.h"

 

classMyRayCastCallback : public b2RayCastCallback {

public:

   NSMutableArray* results;

   NSMutableArray* endResults;

   BOOL resultFlag;

   

   MyRayCastCallback();

   

   float32 ReportFixture(b2Fixture *fixture,const b2Vec2 &point, const b2Vec2 &normal, float32 fraction);

   

   void ClearResults();

   void ResetFlag();

};

实现:

#import"MyRayCastCallback.h"

#import"RayCastResult.h"

 

MyRayCastCallback::MyRayCastCallback(){

   results = [[NSMutableArray alloc] init];

   endResults = [[NSMutableArray alloc] init];

   resultFlag = true;

}

 

float32MyRayCastCallback::ReportFixture(b2Fixture *fixture, const b2Vec2 &point,const b2Vec2 &normal, float32 fraction) {

   if (resultFlag) {

       [results addObject:[[RayCastResultalloc] initWithFixture:fixture point:point normal:normal fraction:fraction]];

   } else {

       [endResults addObject:[[RayCastResultalloc] initWithFixture:fixture point:point normal:normal fraction:fraction]];

   }

   return 1;

}

 

voidMyRayCastCallback::ClearResults() {

   [results removeAllObjects];

   [endResults removeAllObjects];

}

 

voidMyRayCastCallback::ResetFlag() {

   resultFlag = !resultFlag;

}

关于射线投射的原理和使用请参考Box2D中切割刚体效果的实现一览(二),我们上面的这部分实现也是从其中截取出来的。其中RayCastResult类的声明和实现如下:

#import"Box2D.h"

 

@interfaceRayCastResult : NSObject

 

-(id)initWithFixture:(b2Fixture*)fixture

               point:(b2Vec2) point

              normal:(b2Vec2) normal

            fraction:(float32) fraction;

 

@propertyb2Fixture* fixture;

@propertyb2Vec2 point;

@propertyb2Vec2 normal;

@propertyfloat32 fraction;

 

@end

实现:

#import"RayCastResult.h"

 

@implementationRayCastResult

 

@synthesizefixture;

@synthesizepoint;

@synthesizenormal;

@synthesizefraction;

 

-(id)initWithFixture:(b2Fixture*)fixt point:(b2Vec2)p normal:(b2Vec2)n fraction:(float32)f {

   if (self = [super init]) {

       self.fixture = fixt;

       self.point = p;

       self.normal = n;

       self.fraction = f;

   }

   

   return self;

}

 

@end

定义好之后,我们在HelloWorldLayer中添加下面的投射方法:

-(void)doRayCast{

   CGSize size = [[CCDirector sharedDirector]winSize];

   float waterHeight = size.height * 0.4f /PTM_RATIO;

   b2Vec2 waterSurfaceStart(0, waterHeight);

   b2Vec2 waterSurfaceEnd(size.width /PTM_RATIO, waterHeight);

   rayCastCallback.ClearResults();

   world->RayCast(&rayCastCallback,waterSurfaceStart, waterSurfaceEnd);

   rayCastCallback.ResetFlag();

   world->RayCast(&rayCastCallback,waterSurfaceEnd, waterSurfaceStart);

   rayCastCallback.ResetFlag();

}

这个方法在液体表面从左到右和从右到左做两次投射,将结果存储到HelloWorldLayer里我们添加的成员变量中:

MyRayCastCallbackrayCastCallback;

有了投射的方法,我们需要一个方法来根据投射的结果更新物体的状态(isUnderWater和volumnUnderWater),方法如下:

-(void)updateObjectData {

   //遍历两个方向投射得到的交点

   for (RayCastResult* startResult inrayCastCallback.results) {

       for (RayCastResult* endResult inrayCastCallback.endResults) {

           //判断是否是同一个装置(即同一个物体)

           if (startResult.fixture ==endResult.fixture) {

               b2Body* body =startResult.fixture->GetBody();

               //获取物体的UserData,如果UserData不是FloatingObjectData对象,则跳过这个物体

               FloatingObjectData* objectData= (FloatingObjectData*)body->GetUserData();

               if (objectData == nil) {

                   continue;

               }

               

               //得到物体的形状

               b2PolygonShape* shape =(b2PolygonShape*)startResult.fixture->GetShape();

               //物体的顶点数

               int vertexCount =shape->GetVertexCount();

               

               //获取两个投射点的坐标(坐标转换为物体的本地坐标系)

               CGPoint cutPointA = [selftoCGPoint:body->GetLocalPoint(startResult.point)];

               CGPoint cutPointB = [selftoCGPoint:body->GetLocalPoint(endResult.point)];

               

               //判断两个投射点是不是同一个(如果正好和物体相切于1点,那么就只有一个交点)

               if (cutPointA.x == cutPointB.x){

                   //如果只有一个交点,跳过该物体,物体的下一个状态要么是没有交点,要么是有2个交点,到时再判断

                   continue;

               } else {

                   //定义一个数组用来存投射得到的两个点

                   NSMutableArray*underWaterVertexes = [[NSMutableArray alloc] init];

                   

                   //将两个投射点先添加到数组中

                   [underWaterVertexesaddObject:[NSValue valueWithCGPoint:cutPointA]];

                   [underWaterVertexesaddObject:[NSValue valueWithCGPoint:cutPointB]];

                   

                   //遍历物体原来的顶点,将液体中的点添加到数组中

                   for (int i = 0; i <vertexCount; i++) {

                       CGPoint vertex = [selftoCGPoint:shape->GetVertex(i)];

                       

                       //根据行列式的计算结果来确定顶点在液体平面的顺时针一侧还是逆时针一侧(顺时针一侧为液体内部的点)

                       float checkResult =[self calculateDet:cutPointA pointB:cutPointB pointC:vertex];

                       

                       //将符合条件的点添加到数组中

                       if (checkResult < 0){

                           [underWaterVertexesaddObject:[NSValue valueWithCGPoint:vertex]];

                       }

                   }

                   //对顶点进行排序

                   underWaterVertexes = [selfreorderVertexes:underWaterVertexes];

                   //计算液体内部的物体面积

                   objectData.volumnUnderWater= [self calculatePolygonArea:underWaterVertexes];

                   objectData.isUnderWater =true;

               }

           }

       }

   }

}

该方法根据投射的结果更新了所有与液体表面相交的物体的volumnUnderWater属性,方法中添加了详细的注释,在循环的内部有一个calculatePolygonArea方法,用来计算液体内部物体的面积(体积),关于根据凸多边形顶点坐标来计算其面积的算法,请参考根据凸多边形顶点坐标来计算面积算法与实现。下面是涉及到的三个方法:

-(float)calculateTriangleArea:(CGPoint) pointA

                       pointB:(CGPoint) pointB

                       pointC:(CGPoint) pointC{

   float result = [self calculateDet:pointApointB:pointB pointC:pointC] * 0.5f;

   return result > 0 ? result : -result;

}

 

-(float)calculatePolygonArea:(NSMutableArray*) vertexes {

   float result = 0;

   int vertexCount = [vertexes count];

   CGPoint startPoint = [vertexes[0]CGPointValue];

   for (int i = 1; i < vertexCount - 1;i++) {

       result += [selfcalculateTriangleArea:startPoint pointB:[vertexes[i] CGPointValue]pointC:[vertexes[i+1] CGPointValue]];

   }

   

   return result / (PTM_RATIO * PTM_RATIO);

}

 

-(float)calculateDet:(CGPoint) pointA

              pointB:(CGPoint) pointB

              pointC:(CGPoint) pointC {

   return pointA.x * pointB.y + pointB.x *pointC.y + pointC.x * pointA.y

   - pointA.y * pointB.x - pointB.y * pointC.x- pointC.y * pointA.x;

}

顶点的排序方法如下(排序算法请参考Box2D中切割刚体效果的实现一览(完)):

-(NSMutableArray*)reorderVertexes:(NSMutableArray*)vertexes {

   int vertexCount = [vertexes count];

   NSMutableArray* tmpVertexes =[[NSMutableArray alloc] initWithArray:vertexes copyItems:true];

   [vertexes sortUsingComparator:^(id obj1, idobj2) {

       if ([obj1 CGPointValue].x > [obj2CGPointValue].x) {

           return(NSComparisonResult)NSOrderedDescending;

       }

       if ([obj1 CGPointValue].x < [obj2CGPointValue].x) {

           return(NSComparisonResult)NSOrderedAscending;

       }

       return(NSComparisonResult)NSOrderedSame;

   }];

   CGPoint left = [vertexes[0] CGPointValue];

   CGPoint right = [vertexes[vertexCount - 1]CGPointValue];

   int leftPos = 1;

   int rightPos = vertexCount - 1;

   tmpVertexes[0] = vertexes[0];

   for (int i = 1; i < vertexCount - 1;i++) {

       if ([self calculateDet:leftpointB:right pointC:[vertexes[i] CGPointValue]] > 0) {

           tmpVertexes[rightPos--] =vertexes[i];

       } else {

           tmpVertexes[leftPos++] =vertexes[i];

       }

   }

   tmpVertexes[leftPos] = vertexes[vertexCount- 1];

   return tmpVertexes;

}

上面的方法添加完成后,我们在update方法的最后添加上下面的代码:

[selfdoRayCast];

[selfupdateObjectData];

for (b2Body*body = world->GetBodyList(); body; body = body->GetNext()) {

   FloatingObjectData* objectData =(FloatingObjectData*)body->GetUserData();

   if (objectData == nil) {

       continue;

   }

       

   if (objectData.isUnderWater) {

       b2Vec2 bodyVelocity =body->GetLinearVelocity();

       body->SetLinearVelocity(b2Vec2(bodyVelocity.x * 0.999f,bodyVelocity.y * 0.99f));

       body->SetAngularVelocity(body->GetAngularVelocity() * 0.99f);

       float volumn =objectData.volumnUnderWater > 0 ? objectData.volumnUnderWater :body->GetMass() / body->GetFixtureList()->GetDensity();

       float waterForce = 1.0f *fabs(world->GetGravity().y) * volumn;

       body->ApplyForceToCenter(b2Vec2(0,waterForce));

   }

}

这部分代码就比较简单了,首先做射线投射,利用投射结果更新物体状态,然后遍历世界中的所有物体,对于在液体中的物体,根据浮力计算公式来计算它受到的浮力大小,同时,物体在液体由于阻力的存在,它的角速度和线速度也会按照一定的比例变慢,这里我们用了两个系数0.99和0.999来控制,经过调试,这两个系数的模拟效果还比较不错。

好了,制作完成,运行一下,是不是和我们一开始的截图一样了呢?

如果有问题欢迎留言讨论。


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Box2D中切割刚体效果的实现一览(二)

Box2D刚体切割效果的实现原理

教你使用Box2d制作用蜡笔手绘物体的效果(二)

使用Box2d来制作手绘蜡笔风的刚体

教你使用Box2d制作用蜡笔手绘物体的效果(一)

使用Box2d来制作手绘蜡笔风的刚体

cocos2dx+box2d实现物体爆裂效果

1.说明 整个实现参考了网上的flash代码,代码也还没有做优化爆炸点是按照受理点随即角度裂开的,在下面例子中就是用的鼠标click点。对于分裂后的碎块如果太小,则直接丢弃。切分是用的box...

cocos2d实现液体流动效果

  • 2012-11-07 11:04
  • 84KB
  • 下载

COCOS2DX学习之Box2d物理引擎使用之------动态物体的创建

box2d创建运动的物体

cocos2d-x box2d 简单物体(二)

物体的形状,大小,材质,密度,弹性等属性由定制器描述(fixture),有人翻译成夹具。物体碰撞后会根据各自的定制器做出反应。定制器主要属性如下 -形状 -弹性 -摩擦 -密度 物体间的碰撞...

unity2d 实现物体跟随鼠标绕一个点旋转效果

UNITY
  • wayhb
  • wayhb
  • 2016-01-18 11:05
  • 3343

unity2d 实现物体跟随鼠标绕一个点旋转效果

文章转载自:http://www.cnblogs.com/wayhb/p/5138885.html 在2D游戏中,类似泡泡龙炮台发射、敌人飞机永远指向PLAYER、愤怒小鸟弹弓发射等效果,都需要...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)