btHeightfieldTerrainShape not colliding with other bodies.

MachCUBED
Posts: 3
Joined: Sun Apr 10, 2011 5:06 pm

btHeightfieldTerrainShape not colliding with other bodies.

Post by MachCUBED »

Hello everyone,

I'm new to Bullet, and I'm using Bullet 2.7.6 on iOS 4.3.1. My program compiles just fine, but it crashes with the following debugger console message:

Assertion failed: (heightfieldData && "null heightfield data"), function initialize, file /Users/alonzomachiraju/Development/iPad/RocketPenguin/external/bullet/BulletCollision/CollisionShapes/btHeightfieldTerrainShape.cpp, line 64.

The relevant code is the following:

Code: Select all

const char* groundStrIn = [[NSString stringWithContentsOfURL:[[NSBundle mainBundle]URLForResource:@"RaceTrack1Path_512" withExtension:@"png"] encoding:NSUTF8StringEncoding error:nil]UTF8String];
        
btCollisionShape* groundShape = new btHeightfieldTerrainShape(32,32, (char*)groundStrIn, 1.0f, 1.0f, 10.0f, 2, PHY_FLOAT, false);
What am I doing wrong?
Last edited by MachCUBED on Fri Apr 15, 2011 6:35 pm, edited 1 time in total.
User avatar
dphil
Posts: 237
Joined: Tue Jun 29, 2010 10:27 pm

Re: btHeightfieldTerrainShape "null heightfield data" log on

Post by dphil »

It means groundStrIn is a null pointer. Your string derivation looks ok, but the URLForResource method returns nil if it doesn't find a matching file for the supplied URL and extension. So follow the logic in

Code: Select all

[[NSBundle mainBundle]URLForResource:@"RaceTrack1Path_512" withExtension:@"png"]
to make sure the file you are trying to get actually exists in the location you are looking (and that the filename is spelled exactly correctly).
MachCUBED
Posts: 3
Joined: Sun Apr 10, 2011 5:06 pm

Re: btHeightfieldTerrainShape "null heightfield data" log on

Post by MachCUBED »

EDIT: I tried a fix but it didn't work. I added the following method to get the raw data of an image:

Code: Select all

-(float*)getPixelDataFromImage:(UIImage*)image inChannel:(unsigned int)channel
{
    
    [image retain];
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    /*
     * Note we specify 4 bytes per pixel here even though we ignore the
     * alpha value; you can't specify 3 bytes per-pixel.
     */
    
    unsigned int imageWidth = image.size.width; 
    unsigned int imageHeight = image.size.height; 
    
    size_t bytesPerRow = image.size.width * 4; 
    unsigned char * imgData = (unsigned char*)malloc(image.size.height*bytesPerRow);
    
    CGContextRef context =  CGBitmapContextCreate(imgData, image.size.width, image.size.height, 8, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    
    CGColorSpaceRelease(colorSpace); 
    CGContextClearRect(context, CGRectMake(0, 0, imageWidth, imageHeight)); 
    CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), 
                       image.CGImage); 
    CGContextRelease(context); 

    float * terrainHeightData = (float*)malloc(imageWidth * imageHeight * sizeof(float)); 
    
    // Iterate once to get all terrain data needed in a simple array 
    for (int j = 0; j < imageHeight; j++) 
    { 
        for (int i = 0; i < imageWidth; i++) 
        { 
            terrainHeightData[j * imageWidth + i] = imgData[(j * imageWidth + i) * 4 + channel] / 255.0; 
        } 
    } 
    
    /*
     * At this point, we have the raw ARGB pixel data in the imgData buffer, so
     * we can perform whatever image processing here.
     */
    
    //Clean-up, clean-up, everybody does clean-up. :D
    free (imgData); 
    [image release]; 
    
    return terrainHeightData;
}
I then edited the bit of my init: method where my collision object gets created:

Code: Select all


        float* groundDataIn = [self getPixelDataFromImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"RaceTrack1Path_512" ofType:@"png"]] inChannel:2];
        
       NSLog(@"Heightfield data:%@", [NSString stringWithFormat:@"%f", groundDataIn, nil]);
        
        btCollisionShape* groundShape = new btHeightfieldTerrainShape(512,512, (float*)groundDataIn, 1.0f, 0.0f, 10.0f, 2, PHY_FLOAT, false);
I have a consistent framerate of 60fps, but my balls and boxes aren't colliding with my terrain mesh. This appears to be because my heightfield data is 0.000000 for some reason. What's going on?

Original Message:

It turns out I should've used pathForResouce: ofTyppe: in my code, since it's a local file I'm trying to access. Here's the fix:

Code: Select all

const char* groundStrIn = [[[NSBundle mainBundle]pathForResource:@"RaceTrack1Path_512" ofType:@"png"]UTF8String];
However, although my code compiles fine, the app runs with an acceptable framerate, and it stable, there is currently no collision detection occurring between the terrain mesh and the rest of the physics world. Here is the relevant code:

Code: Select all

const char* groundStrIn = [[[NSBundle mainBundle]pathForResource:@"RaceTrack1Path_512" ofType:@"png"]UTF8String];
        
        btCollisionShape* groundShape = new btHeightfieldTerrainShape(512,512, (char*)groundStrIn, 1.0f, 0.0f, 10.0f, 2, PHY_FLOAT, false);
Do I have to extract the UIImage's raw data to make it work?
MachCUBED
Posts: 3
Joined: Sun Apr 10, 2011 5:06 pm

Re: btHeightfieldTerrainShape not colliding with other bodie

Post by MachCUBED »

I tried another approach, thinking that my array data wasn't being returned by the method I tried earlier. What I did was I first made float* groundDataIn into a class member, so now it's in my @interface declaration, like this:

Code: Select all

@interface RocketPenguinTestView : Isgl3dBasic3DView {
 ...
   float* groundDataIn;
}

EDIT: I changed my implementation with the following methods

New init: method:

Code: Select all

- (id) init {
	
	if ((self = [super init])) {
		_physicsObjects = [[NSMutableArray alloc] init];
	 	_lastStepTime = [[NSDate alloc] init];
        
	 	srandom(time(NULL));
        
		// Create and configure touch-screen camera controller
		_cameraController = [[Isgl3dDemoCameraController alloc] initWithCamera:self.camera andView:self];
		_cameraController.orbit = 16;
		_cameraController.theta = 30;
		_cameraController.phi = 30;
		_cameraController.doubleTapEnabled = NO;
		
		// Enable shadow rendering
		[Isgl3dDirector sharedInstance].shadowRenderingMethod = Isgl3dShadowPlanar;
		[Isgl3dDirector sharedInstance].shadowAlpha = 0.4;
        
        
		// Create physics world with discrete dynamics
		_collisionConfig = new btDefaultCollisionConfiguration();
		_broadphase = new btDbvtBroadphase();
		_collisionDispatcher = new btCollisionDispatcher(_collisionConfig);
		_constraintSolver = new btSequentialImpulseConstraintSolver;
		_discreteDynamicsWorld = new btDiscreteDynamicsWorld(_collisionDispatcher, _broadphase, _constraintSolver, _collisionConfig);
		_discreteDynamicsWorld->setGravity(btVector3(0,-10,0));
        
		_physicsWorld = [[Isgl3dPhysicsWorld alloc] init];
		[_physicsWorld setDiscreteDynamicsWorld:_discreteDynamicsWorld];
		[self.scene addChild:_physicsWorld];
        
		// Create textures
		_beachBallMaterial = [[Isgl3dTextureMaterial alloc] initWithTextureFile:@"BeachBall.png" shininess:0.9 precision:TEXTURE_MATERIAL_MEDIUM_PRECISION repeatX:NO repeatY:NO];
		_isglLogo = [[Isgl3dTextureMaterial alloc] initWithTextureFile:@"cardboard.jpg" shininess:0.9 precision:TEXTURE_MATERIAL_MEDIUM_PRECISION repeatX:NO repeatY:NO];
        
        // The next is for the terrain heightmap.
		Isgl3dTextureMaterial * textureMaterial = [[Isgl3dTextureMaterial alloc] initWithTextureFile:@"RaceTrack1Terrain_1024.png" shininess:0 precision:TEXTURE_MATERIAL_MEDIUM_PRECISION repeatX:NO repeatY:NO];
        
		float radius = 1.0;
		float width = 2.0;
        
		_sphereMesh = [[Isgl3dSphere alloc] initWithGeometry:radius longs:16 lats:16];
		_cubeMesh = [[Isgl3dCube alloc] initWithGeometry:width height:width depth:width nx:2 ny:2];
        
		// Create two nodes for the different meshes
		_cubesNode = [[_physicsWorld createNode] retain];
		_spheresNode = [[_physicsWorld createNode] retain];
        
        // Create the terrain mesh
        Isgl3dTerrainMesh * terrainMesh = [[Isgl3dTerrainMesh alloc] initWithTerrainDataFile:@"RaceTrack1Path_512.png" channel:2 width:32 depth:32 height:10 nx:32 nz:32];
        
        [self createTerrainWithMesh:terrainMesh andMaterial:textureMaterial];
        
        // Add light
        _light  = [[Isgl3dShadowCastingLight alloc] initWithHexColor:@"111111" diffuseColor:@"FFFFFF" specularColor:@"FFFFFF" attenuation:0.003];
        [self.scene addChild:_light];
        [_light setTranslation:10 y:20 z:10];
        
        _light.planarShadowsNode = _terrainPhysicsObject.node;
        
        [self setSceneAmbient:@"666666"];
		
		// Schedule updates
		[self schedule:@selector(tick:)];
	}
	
	return self;
}
New createTerrainWithMesh: method:

Code: Select all

-(void)createTerrainWithMesh:(Isgl3dTerrainMesh*)mesh andMaterial:(Isgl3dTextureMaterial*)material {
    
    // Create UIImage
    
	UIImage * terrainDataImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"RaceTrack1Path_512" ofType:@"png"]];

    //Get the raw data of the material image.
    
    /*
     * Note we specify 4 bytes per pixel here even though we ignore the
     * alpha value; you can't specify 3 bytes per-pixel.
     */
    
    unsigned int imageWidth = terrainDataImage.size.width;  
	unsigned int imageHeight = terrainDataImage.size.height;   
	unsigned char * pixelData = (unsigned char*)malloc(imageWidth * imageHeight * 4); 
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pixelData, imageWidth, imageHeight, 8, imageWidth * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
	CGColorSpaceRelease(colorSpace);
	CGContextClearRect(context, CGRectMake(0, 0, imageWidth, imageHeight));
	CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), terrainDataImage.CGImage);  
	CGContextRelease(context);  
    
    // Create array of heights
    
    groundDataIn = (float*)malloc(imageWidth * imageHeight * sizeof(float)); 
    
    // Iterate once to get all terrain data needed in a simple array 
    for (int j = 0; j < imageHeight; j++) 
    { 
        for (int i = 0; i < imageWidth; i++) 
        { 
            groundDataIn[j * imageWidth + i] = pixelData[(j * imageWidth + i) * 4 + 2] / 255.0; 
            printf("Data at index %d: %f \n", j * imageWidth + i, groundDataIn[j * imageWidth + i]);
        } 
    } 
    
    free (pixelData); 
    
    //Add the mesh to the physics world
    
    Isgl3dMeshNode * node = [_terrain createNodeWithMesh:mesh andMaterial:material];
    
    //Create the collision shape
    
    btCollisionShape* groundShape = new btHeightfieldTerrainShape(512,512, groundDataIn, 1.0f, 0.0f, 10.0f, 2, PHY_FLOAT, false);
    
    [self createPhysicsObject:node shape:groundShape mass:0 restitution:0.6 isFalling:NO];   
    node.enableShadowCasting = YES;
    
    _terrain = [_physicsWorld createNodeWithMesh:mesh andMaterial:material];

}
The printf statement confirmed that the groundDataIn array of floats has perfectly valid values in it. Also, the following line used have groundDataIn typecast as a float when being passed in, but a look at the TerrainDemo example showed that it's unnecessary.

Code: Select all

btCollisionShape* groundShape = new btHeightfieldTerrainShape(512,512, groundDataIn, 1.0f, 0.0f, 10.0f, 2, PHY_FLOAT, false); 
However, there's still no collision detection between the terrain and anything else. What am I doing wrong? I'm at a loss and can't figure this out at all. Argh!

Original code

Adding it as a class method without merging the code from my getPixelDataFromImage: method didn't help matters, so I suspected that my array wasn't being returned properly. Therefore, I took the code from my method and added it to my init: method, like so:

Code: Select all

//Test new approach to populating terrain collision object array
        
        UIImage *terrainImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"RaceTrack1Path_512" ofType:@"png"]];
        [terrainImage retain];
        
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        
        /*
         * Note we specify 4 bytes per pixel here even though we ignore the
         * alpha value; you can't specify 3 bytes per-pixel.
         */
        
        unsigned int imageWidth = terrainImage.size.width; 
        unsigned int imageHeight = terrainImage.size.height; 
        
        size_t bytesPerRow = terrainImage.size.width * 4; 
        unsigned char * imgData = (unsigned char*)malloc(terrainImage.size.height*bytesPerRow);
        
        CGContextRef context =  CGBitmapContextCreate(imgData, terrainImage.size.width, terrainImage.size.height, 8, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
        
        CGColorSpaceRelease(colorSpace); 
        CGContextClearRect(context, CGRectMake(0, 0, imageWidth, imageHeight)); 
        CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), 
                           terrainImage.CGImage); 
        CGContextRelease(context); 
        
        groundDataIn = (float*)malloc(imageWidth * imageHeight * sizeof(float)); 
        
        // Iterate once to get all terrain data needed in a simple array 
        for (int j = 0; j < imageHeight; j++) 
        { 
            for (int i = 0; i < imageWidth; i++) 
            { 
                groundDataIn[j * imageWidth + i] = imgData[(j * imageWidth + i) * 4 + 2] / 255.0; 
            } 
        } 
        
        free (imgData); 
        [terrainImage release]; 
        
        btCollisionShape* groundShape = new btHeightfieldTerrainShape(512,512, (float*)groundDataIn, 1.0f, 0.0f, 10.0f, 2, PHY_FLOAT, false);
Unfortunately, the height field still doesn't have a working collision mesh. Other objects, such as balls and boxes, simply pass through my heightfield without colliding. Where did I go wrong? Is my array interator wrong? Did I mess up the terrain image stuff? Is there an error somewhere else in the code that I didn't think of?

BTW, I tried looking through the TerrainDemo, but I didn't get much out of it since it doesn't load a heightmap from an image file like I'm trying to do.