Using ImageBasedHeightMap

This issue was mentioned in a separate thread: http://hub.jmonkeyengine.org/groups/general-2/forum/topic/heightmap-problems/



I was having the same issue and after digging around the code for a while, I found out the problem: basically, ImageBasedHeightMap access the pixel data using the ByteBuffer stored in com.jme3.texture.Image. The Desktop version of the texture loader, AWTLoader, populates the ByteBuffer properly so things work properly. However, the android version of the texture loader, AndroidImageLoader, does not populate the ByteBuffer. This causes the ImageBasedHeightMap to throw a NullPointerException when trying to access the pixel data.



The reason why the android loader does not populate the ByteBuffer is because android deals with images differently and the android specific Bitmap class that is used to deal with images does not provide direct access to the buffer. After playing around for a while, I came up with the following class for loading image based heightmaps in android:



[java]

public class AndroidImageBasedHeightMap extends ImageBasedHeightMap {



public AndroidImageBasedHeightMap() {

super(null);

}



public AndroidImageBasedHeightMap(Image colorImage) {

super(colorImage);

}



public AndroidImageBasedHeightMap(Image colorImage, float heightScale) {

super(colorImage, heightScale);

}



@Override

protected float getHeightAtPostion(ByteBuffer buf, Image image, int position, ColorRGBA store) {

if (buf != null) {

return super.getHeightAtPostion(buf, image, position, store);

} else {

AndroidImageInfo imageInfo = (AndroidImageInfo) image.getEfficentData();

Bitmap bitmap = imageInfo.getBitmap();

int y = position / bitmap.getWidth();

int x = position - (y * bitmap.getWidth());

int color = bitmap.getPixel(x, y);

float h = Color.red(color) * heightScale;

//System.out.println(h);

return h;

}

}



}

[/java]



The basic idea is to override the getHeightAtPosition method so that in the android case, we use the android specific way to access the pixel data.



Currently in my code, I use the following code to determine with ImageBasedHeightMap implementation to use:



[java]

ImageBasedHeightMap heightmap;

try {

Class c = Class.forName("com.mycompany.mygame.AndroidImageBasedHeightMap");

heightmap = (ImageBasedHeightMap) c.newInstance();

heightmap.setImage(heightMapImage.getImage());

} catch (Exception ex) {

heightmap = new ImageBasedHeightMap(heightMapImage.getImage());

}

[/java]



This is of course less than ideal as the platform mechanism should be abstracted away alongside asset loading, rendering, etc. What do you guys think is the best way to deal with this problem?



Thanks!



Jason

4 Likes

Cool, thanks!

Does something like this make sense?



AndroidImageLoader.java

[java]

This patch file was generated by NetBeans IDE

Following Index: paths are relative to: D:UserspotterecDocumentsjMonkeyProjectsjME3_Reposrcandroidcomjme3textureplugins

This patch can be applied using context Tools: Patch action on respective folder.

It uses platform neutral UTF-8 encoding and n newlines.

Above lines and this line are ignored by the patching process.

Index: AndroidImageLoader.java

— AndroidImageLoader.java Base (BASE)

+++ AndroidImageLoader.java Locally Modified (Based On LOCAL)

@@ -13,7 +13,7 @@

AndroidImageInfo imageInfo = new AndroidImageInfo(info);

Bitmap bitmap = imageInfo.getBitmap();


  •    Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null);<br />
    
  •    Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), imageInfo.getBuffer());<br />
    

image.setEfficentData(imageInfo);

return image;

}

[/java]



AndroidImageInfo.java

[java]

This patch file was generated by NetBeans IDE

Following Index: paths are relative to: D:UserspotterecDocumentsjMonkeyProjectsjME3_Reposrcandroidcomjme3asset

This patch can be applied using context Tools: Patch action on respective folder.

It uses platform neutral UTF-8 encoding and n newlines.

Above lines and this line are ignored by the patching process.

Index: AndroidImageInfo.java

— AndroidImageInfo.java Base (BASE)

+++ AndroidImageInfo.java Locally Modified (Based On LOCAL)

@@ -5,8 +5,10 @@

import android.graphics.Matrix;

import com.jme3.texture.Image;

import com.jme3.texture.Image.Format;

+import com.jme3.util.BufferUtils;

import java.io.IOException;

import java.io.InputStream;

+import java.nio.ByteBuffer;

import java.util.logging.Level;

import java.util.logging.Logger;



@@ -25,6 +27,7 @@

protected AssetInfo assetInfo;

protected Bitmap bitmap;

protected Format format;

  • protected ByteBuffer buffer;



    public AndroidImageInfo(AssetInfo assetInfo) {

    this.assetInfo = assetInfo;

    @@ -49,6 +52,8 @@

    if (bitmap != null && !bitmap.isRecycled()) {

    bitmap.recycle();

    bitmap = null;
  •        BufferUtils.destroyDirectBuffer(buffer);<br />
    
  •        buffer = null;<br />
    

logger.log(Level.INFO, "Bitmap was deleted. ");

}

}

@@ -57,12 +62,17 @@

return format;

}


  • public ByteBuffer getBuffer(){
  •    return buffer;<br />
    
  • }

    +

    /**
  • Loads the bitmap directly from the asset info, possibly updating
  • or creating the image object.

    */

    protected void loadBitmap() throws IOException{

    InputStream in = null;
  •    int size = 0;<br />
    

try {

in = assetInfo.openStream();

bitmap = BitmapFactory.decodeStream(in);

@@ -76,23 +86,29 @@

}



switch (bitmap.getConfig()) {

+

case ALPHA_8:

format = Image.Format.Alpha8;

  •            size = bitmap.getWidth() * bitmap.getHeight() * 1;<br />
    

break;

case ARGB_4444:

format = Image.Format.ARGB4444;

  •            size = bitmap.getWidth() * bitmap.getHeight() * 2;<br />
    

break;

case ARGB_8888:

format = Image.Format.RGBA8;

  •            size = bitmap.getWidth() * bitmap.getHeight() * 4;<br />
    

break;

case RGB_565:

format = Image.Format.RGB565;

  •            size = bitmap.getWidth() * bitmap.getHeight() * 2;<br />
    

break;

default:

// This should still work as long

// as renderer doesn’t check format

// but just loads bitmap directly.

format = null;

  •            size = bitmap.getWidth() * bitmap.getHeight() * 4;<br />
    

}



TextureKey texKey = (TextureKey) assetInfo.getKey();

@@ -108,5 +124,15 @@

throw new IOException("Failed to flip image: " + texKey);

}

}

+

  •    if (buffer != null) {<br />
    
  •        BufferUtils.destroyDirectBuffer(buffer);<br />
    

}

  •    if (bitmap != null &amp;&amp; size &gt; 0) {<br />
    
  •        buffer = BufferUtils.createByteBuffer(size);<br />
    
  •        bitmap.copyPixelsToBuffer(buffer);<br />
    
  •    } else {<br />
    
  •        buffer = null;<br />
    

}

  • }

    +}

    [/java]

Thanks @iwgeric! I tried something similar to your solution, but I got an out of memory error when I tried to run my code. I believe that the reason was because the above approach allocates a direct buffer for each image used in the program. Is the above code working well for you?

@batkid



just wanted to make sure you knew there was another conversation going on about ImageBasedHeightMap on Android. I also got a memory problem with all the images in the scene had buffers created. Take a look at the post below.



http://hub.jmonkeyengine.org/groups/android/forum/topic/imagedbasedheightmap-on-android/

1 Like

This is hardly an ideal solution, as it unnecessarily generates ByteBuffers for images even though it might not be used at all. Only 1% of the textures used by an app might be used for heightmaps, the rest are simply regular textures. All of that memory is thus wasted.

@Momoko_Fan said:
This is hardly an ideal solution, as it unnecessarily generates ByteBuffers for images even though it might not be used at all. Only 1% of the textures used by an app might be used for heightmaps, the rest are simply regular textures. All of that memory is thus wasted.


um, yeah, I agree. That's why the reference to the other post. There is a different proposal there which overrides the getData method to only create the buffer on demand with a weak reference.

@batkid: i am using j monkey 3.1 and i am new to j monkey, I can not override getHeightAtPosition method of ImageBasedHeightMap Because there is no method called getHeightAtPosition in ImageBasedHeightMap class.

A lot changes in three years. Maybe take a look at the javadoc and see if you can find a similar method:
http://javadoc.jmonkeyengine.org/com/jme3/terrain/heightmap/ImageBasedHeightMap.html