使用Objective-C和C++避免并行数组

Avoiding parallel arrays with Objective-C and C++

本文关键字:并行 数组 C++ Objective-C 使用      更新时间:2023-10-16

一些上下文:我正在为Cocos2D/Box2D编写一个2D可破坏地形库,它涉及C++和Objective-C的混合。我最近遇到了一个困扰我的情况,我想不出一个不涉及并行数组的解决方案。

为了在地形周围定义物理边界,必须对地形边界进行初始跟踪。我调用了一个函数来跟踪纹理中所有孤立的像素体并缓存它们。这创建了以下数据结构

  • 一个NSMutableDictionary"borderPixels",其键等于一个CGPoint,该CGPoint封装在NSValue中,该NSValue等于像素的唯一位置。这将保存所有跟踪的像素
  • TerPixels指向下一个相邻像素的循环链表
  • NSMutableArray"traceBodyPoints",它包含一个表示地形体"起点"的TerPixel。我只在这里存储TerPixel*,我需要在那里追踪一个物理体。因此,如果一个地形实体已经被修改,我将修改后的实体中的任何单个TerPixel*插入到这个数组中。然后我可以引用其中的每一个,并遍历链接列表来跟踪物理体

以下是一些有助于更好地描述情况的代码:

-(void)traverseBoundaryPoints:(CGPoint)startPt {
if (!borderPixels) borderPixels = [[NSMutableDictionary alloc] init]; // Temp
if (!traceBodyPoints) traceBodyPoints = [[NSMutableArray alloc] init]; // Temp
TerPixel * start = [[TerPixel alloc] initWithCoords:startPt.x ypt:startPt.y prevx:-1 prevy:0];
TerPixel * next = start;
//CCLOG(@"Start of traverseBoundary next.x and next.y %d, %d", next.x, next.y);
TerPixel * old;

while (true) {
    old = next;
    next = [self findNextBoundaryPixel:next];
    [next setNSP:[old subTerPixel:next]];
    old.nextBorderPixel = next;
    if (next.x == start.x && next.y == start.y) {
        CCLOG(@"SUCCESS :: start = next");
        next.nextBorderPixel = start; // Make the linked list circular
        NSValue * pixLocVal = [next getAsValueWithPoint];
        [borderPixels setObject:next forKey:pixLocVal];
        // Add the pixel to the tracePoints array to be traversed/traced later
        [traceBodyPoints addObject:start];
        break;
    } // end if
    // Connect the linked list components
    NSValue * pixLocVal = [next getAsValueWithPoint];
    [borderPixels setObject:next forKey:pixLocVal];
} // end while
} // end traverse function

这就是我找不到解决方案的地方。我需要将traceBodyPoints数组中的每个TerPixel*与Box2D b2Body关联起来,该实体将被创建并添加到物理世界中。在我的库中,纹理中的每个孤立像素体都对应于Box2D体。所以,当一个事件发生,摧毁了一大块地形时,我需要摧毁与被摧毁的像素相关的物体,只追溯被改变的物体。这意味着我需要一种方法来将任何给定的TerPixel*与Box2D实体*相关联。

据我所知,在带有ARC的Objective-C中,如果不将C++对象/指针桥接到void*,我就无法将其包含在Objective-C容器中。问题是,这些操作需要具有令人难以置信的性能,并且从事桥梁铸造成本非常高。此外,我不想在每个TerPixel中都包含指向Box2D主体的指针。这将是一场噩梦,以确保没有悬挂的指针,并需要无意义的迭代来消除指针。

以下是我创建物理边界的逻辑

-(void)createPhysicsBoundaries {
if ([self->traceBodyPoints count] == 0)  {
    CCLOG(@"createPhysicsBoundaries-> No bodies to trace");
    return;
}
// NEED LOGIC HERE TO DELETE ALTERED BODIES
// NEED TO DELETE EXISTING BOX2D BODY AND RE-TRACE A NEW ONE
// For each body that has been altered, traverse linked list to trace the body
for (TerPixel * startPixel in self->traceBodyPoints) {
    TerPixel * tracePixel = startPixel.nextBorderPixel;
    b2BodyDef tDef;
    tDef.position.Set(0, 0);
    b2Body * b = self->world->CreateBody(&tDef);
    self->groundBodies->push_back(b);
    b->SetUserData((__bridge void*) self);
    b2EdgeShape edgeShape;
    CCLOG(@"StartPixel %d, %d", startPixel.x, startPixel.y);
    while (tracePixel != startPixel) {
        b2Vec2 start = b2Vec2(tracePixel.x/PTM_RATIO, tracePixel.y/PTM_RATIO);
        //CCLOG(@"TracePixel BEFORE %d, %d", tracePixel.x, tracePixel.y);
        tracePixel = tracePixel.nextBorderPixel;
        //CCLOG(@"TracePixel AFTER %d, %d", tracePixel.x, tracePixel.y);
        b2Vec2 end = b2Vec2(tracePixel.x/PTM_RATIO, tracePixel.y/PTM_RATIO);
        edgeShape.Set(start,end);
        b->CreateFixture(&edgeShape, 0);
    } // end while
} // end for
} // end createPhysicsBoundaries

希望这是有道理的。如果你需要看到正在发生的事情,这里有一个视频。http://www.youtube.com/watch?v=IUsgjYLr6e0&feature=youtu.be,其中绿色边界是物理边界。

从事桥梁铸造是非常昂贵的

谁说的?它仍然只是一个演员阵容,基本上是免费的/可以忽略不计的。桥接转移或保留强制转换会添加相应的引用计数方法调用,但这里不需要。

解决你的问题真的很简单。您有一个包含TerPixel类实例的traceBodyPoints数组。

您只需要为这个数组提供一个包装器类,我们称之为TerrainBlob。TerrainBlob类包含您的NSMutableArray traceBodyPoints属性和b2Body:的另一个属性

@interface TerrainBlob : NSObject
@property NSMutableArray* traceBodyPoints;
@property b2Body* body;
@end

您可以改进TerPixel以包含对TerrainBlob的反向引用,TerrainBlob必须是弱的以避免保留循环。这样每个像素都可以访问b2Body。您也可以在TerrainBlob中添加一个方法,该方法添加一个TerPixel,并为方便起见正确设置TerrainBlob属性。

@interface TerPixel : NSObject
...
@property (weak) TerrainBlob* terrainBlob;
@end

然后,您可以从TerPixel:中访问b2Body

b2Body* body = _terrainBlob.body;
if (body)
{
    // do something with body
}

现在您只需要在一个位置更新主体,每个TerPixel在使用之前都需要检查主体是否为nil

最后,值得一提的是,基于像素的可破坏地形过于致命,尤其是在Retina设备上。除非你已经在做了,否则考虑创建跨越多个像素的近似线形,因为物理模拟并不需要像素的完美精度。