0
votes

Some context: I am writing a 2D Destructible terrain library for Cocos2D/Box2D which involves a hybrid of C++ and Objective-C. I recently encountered a situation that has stumped me where I can't think of a solution that doesn't involve parallel arrays.

In order for the physics boundaries to be defined around the terrain, an initial trace must be made of the terrain border. I make a function call to trace all isolated bodies of pixels within a texture and cache them. This creates the following data structures

  • An NSMutableDictionary "borderPixels" with the key being equal to an CGPoint wrapped in an NSValue which is equal to the pixel's unique location. This holds all traced pixels.
  • A circular linked lists with TerPixels pointing to their next neighbor pixel
  • An NSMutableArray "traceBodyPoints" which holds a single TerPixel representing a 'start' point of a terrain body. I only store TerPixel *'s here where I need to trace a physics body. So, if a terrain body has been modified, I insert any individual TerPixel * from the modified body into this array. I can then reference each of these and traverse the linked list to trace the physics body.

Here is some code to help paint a better picture of the situation:

-(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

Here is where I can't find a solution. I need to relate each TerPixel * in the traceBodyPoints array to a Box2D b2Body which will be created and added to the physics world. In my library, each isolated body of pixels within a texture corresponds to a Box2D body. So, when an event happens that destroys a chunk of the terrain, I need to destroy the body associated the the destroyed pixels and retrace ONLY the altered bodies. This means I need a way to associate any given TerPixel * to a Box2D body *.

In Objective-C with ARC, to my knowledge, I cannot include C++ objects/pointers in Objective-C containers without bridge casting to void *'s. Problem is these operations need to be incredibly performant and engaging in bridge casting is very costly. Also, I don't want to include a pointer to a Box2D body in every single TerPixel. This would be a nightmare to ensure there are no dangling pointers and require pointless iteration to nil pointers out.

Here is my logic for creating physics boundaries

-(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

Hopefully this makes sense. If you need a visual of what is happening, here is a video. http://www.youtube.com/watch?v=IUsgjYLr6e0&feature=youtu.be where the green boundaries are physics boundaries.

1
Have you considered Chipmunk Indie/Pro? It has a feature called autogeometry for destructible terrain built-in. youtube.com/watch?v=oacZwUGP11cLearnCocos2D
No I had never heard of it. I've been wanting a Box2D Destructible Terrain solution for a while. I am about 95% done with the solution... So I suppose I might as well finish it. The library I'm building allows for users to treat destruction of terrain and redefining of physics with one call and managers multi-body collisions. In retrospect, I should have looked around more I suppose. Btw, read your book. Great read!Paul Renton

1 Answers

2
votes

engaging in bridge casting is very costly

Says who? It's still just a cast and essentially free/negligible. A bridge transfer or retain cast adds the corresponding reference counting method calls, but you don't need that here.

The solution to your problem is really simple. You have traceBodyPoints array containing instances of the TerPixel class.

You simply need a wrapper class for this array, let's call it TerrainBlob. The TerrainBlob class contains as property your NSMutableArray traceBodyPoints and another property for the b2Body:

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

You can improve TerPixel to contain a back reference to the TerrainBlob, which must be weak to avoid retain cycles. That way each pixel can access the b2Body. You could also add a method in TerrainBlob that adds a TerPixel and properly sets the terrainBlob property for convenience.

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

You can then access the b2Body from within a TerPixel:

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

Now you only need to update body in one location, and each TerPixel needs to check whether the body is nil before using it.

Finally, it is worth mentioning that destructible terrain on pixel basis is overkill, especially on Retina devices. Unless you're already doing that, look into creating approximate line shapes spanning multiple pixels because you don't really need pixel-perfect accuracy for the physics simulation.