我们继续来实现切割效果。
我们现在有了切割线和多边形,可以利用Box2d的射线投射(RayCast)来检测切割线和多边形的交点(入射点)。在Box2d中通过定义b2RayCastCallback的子类来获取射线投射的结果,关于Box2d中的射线投射的使用,可以参考Box2Dv2.3.0 用户指南(第十章)。
我们创建b2RayCastCallback的子类MyRayCastCallback,声明如下:
#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();
};
成员中定义了两个数组成员,results和endResults,原因是Box2d中射线投射得到的是线段射入多边形的第一个入射点,而实际上我们需要两个切点,因此我们的思路是做两次射线投射,第一次从lineStartSave到lineEndSave,第二次相反。从而得到两组入射点,分别记录在results和endResults两个数组中。
resultFlag用来区分两次投射。方法ResetFlag用于反置resultFlag。ClearResults方法用来清空投射结果。ReportFixture方法用来记录投射结果。
接着我们定义一个类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
类中定义了4个属性,分别对应投射得到的装置,入射点,法线和比例系数。
类的定义(实现)如下:
#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
没有太多需要说明的,定义了一个构造函数用来初始化所有属性。
接着我们来看MyRayCastCallback类的定义:
#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:[[RayCastResult alloc] initWithFixture:fixture point:pointnormal:normal fraction:fraction]];
} else {
[endResults addObject:[[RayCastResult alloc] initWithFixture:fixturepoint:point normal:normal fraction:fraction]];
}
return 1;
}
voidMyRayCastCallback::ClearResults() {
[results removeAllObjects];
[endResults removeAllObjects];
}
voidMyRayCastCallback::ResetFlag() {
resultFlag = !resultFlag;
}
ReportFixture方法中通过对resultFlag的判断来确定结果应该存放到results中还是endResults中,方法返回1,这样当切割线跨过多个多边形的时候,能够捕获到所有的切点。其他的方法没有太多复杂的逻辑,不做说明了。
接着我们在HelloWorldLayer中添加一个成员:
MyRayCastCallbackrayCastCallback;
然后定义下面的方法来表示一次RayCast的执行:
-(void)doRayCast{
rayCastCallback.ClearResults();
world->RayCast(&rayCastCallback, [self toVec:lineStartSave], [selftoVec:lineEndSave]);
rayCastCallback.ResetFlag();
world->RayCast(&rayCastCallback, [self toVec:lineEndSave], [selftoVec:lineStartSave]);
rayCastCallback.ResetFlag();
}
首先清空结果,然后执行第一次RayCast,反置resultFlag,然后执行第二次RayCast得到反向的切点,再次反置resultFlag。执行完这个方法后,所有的切点都储存在rayCastCallback的results和endResults中了。
正常情况下,我们希望在每次拖拽出切割线并松开后来执行射线投射的计算,因此应该在ccTouchEnded方法中添加doRayCast的调用。但是这里我们希望更加直观地看到切点的效果,所以我们在ccTouchMoved方法中添加调用,这样当切割线随着触点的移动,会实时地计算所有切点。接着我们在draw方法中添加下面的代码,在每个切点上绘制一个圆形来直观地表现其位置:
if([rayCastCallback.results count] > 0) {
for (RayCastResult* result in rayCastCallback.results) {
CGPoint point = [self toCGPoint:result.point];
ccDrawCircle(point, 3, 0, 10, YES);
}
ccDrawColor4F(255, 0, 0, 255);
for (RayCastResult* result in rayCastCallback.endResults) {
CGPoint point = [self toCGPoint:result.point];
ccDrawCircle(point, 3, 0, 10, YES);
}
}
这里正向和反向投射得到的切点我们用不同的颜色来绘制,反向的切点用红色(255,0,0),正向的用白色。使用ccDrawCircle方法来绘制圆形。
由于Box2d中使用的长度单位不是像素,因此我们实现了两个b2Vec2和CGPoint之间转换的函数来方便调用:
-(b2Vec2)toVec:(CGPoint)p {
b2Vec2 result(p.x / (float32)PTM_RATIO, p.y / (float32)PTM_RATIO);
return result;
}
-(CGPoint)toCGPoint:(b2Vec2)vec {
return CGPointMake(vec.x * (float32)PTM_RATIO, vec.y * (float32)PTM_RATIO);
}
实现后得到的运行效果(我们添加了3个多边形):
先到这里,最后一篇中我们来完成刚体的切割效果的制作。