here is a simple area based MipMapper implementation based on Egon's one. it uses java5 syntax but not java5 specific classes so can be converted to earlier versions of java
to use, create an instance of MipMapper and add Object3D's to be mip mapped with addObject(Object3D). call MipMapper.mipMap(World, FrameBuffer) either before or after render/draw.
please note:
* it requires the upcoming version (1.11) of jPCT
* it isnt suitable for textures which are modified or replaced over time
r a f t
import com.threed.jpct.*;
import java.util.*;
import java.awt.*;
import java.awt.image.*;
/**
* a simple area based mip mapper implementation.
* i.e: mip map level per polygon is decided according to ratio:
* area of polygon on screen / area of polygon on texture
*
* @author r a f t
*/
public class MipMapper {
/** the number of mipmap levels */
public static final int LEVELS = 8; // enough for mip mapping a 1024x1024 texture to 8x8
/** the minimum mipmap size (lower isn't possible, higher is) */
public static final int MIN_SIZE = 8;
/** original texture will be used dwon to this ratio (polygon area / texture area) */
public static final float FIRST_LEVEL = 1/2.5f;
/** debug switch for showing colored mip maps */
public static boolean colorMipmaps = false;
private final TextureManager tm = TextureManager.getInstance();
private final int[] visListData = new int[2];
private final BitSet changedObjects = new BitSet(256);
private final BitSet processedTextures = new BitSet(256);
private final Map<Integer, ObjectData> mipMapData = new HashMap<Integer, ObjectData>();
private final Map<String, Integer> textureIds = new HashMap<String, Integer>();
private final Set<String> createdTextures = new HashSet<String>();
public MipMapper() {}
/** adds the object to mipmap list */
public void addObject(Object3D object3d) {
mipMapData.put(object3d.getID(), new ObjectData(object3d));
}
/** removes the object from mipmap list */
public void removeObject(int id) {
mipMapData.remove(id);
}
/** creates mipmap textures if they arent already created */
private void createMipMaps(Object3D object3d) {
PolygonManager pm = object3d.getPolygonManager();
for (int polygonId = 0; polygonId < pm.getMaxPolygonID(); polygonId++) {
int textureId = pm.getPolygonTexture(polygonId);
if (!processedTextures.get(textureId)) {
Texture texture = tm.getTextureByID(textureId);
int owidth = texture.getWidth();
int oheight = texture.getHeight();
TexelGrabber tg = new TexelGrabber();
texture.setEffect(tg);
texture.applyEffect();
texture.removeEffect();
int[] texels = tg.texels;
BufferedImage origTextureImage = new BufferedImage(owidth, oheight, BufferedImage.TYPE_INT_RGB);
int[] pixels = ((DataBufferInt) ((BufferedImage) origTextureImage).getRaster().getDataBuffer()).getData();
Texture lastTexture = texture;
int lastTextureId = textureId;
for (int offset = 1; offset < LEVELS; offset++) {
int width = (int) ((float) owidth/Math.pow(2, offset));
int height = (int) ((float) oheight/Math.pow(2, offset));
width = Math.max(MIN_SIZE, width);
height = Math.max(MIN_SIZE, height);
if ((lastTexture.getWidth() != width) || (lastTexture.getHeight() != height)) {
if (colorMipmaps) {
int[] tmpy = new int[texels.length];
int col = 200<<(8*((offset-1)%3));
for (int i = 0; i < tmpy.length; i++) {
tmpy[i] = col;
}
texels = tmpy;
}
System.arraycopy(texels, 0, pixels, 0, owidth*oheight);
Image mipmapTextureImage = origTextureImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
Texture mipmapTexture = new Texture(mipmapTextureImage);
String textureName = getTextureName(textureId, offset);
if (tm.containsTexture(textureName))
tm.replaceTexture(textureName, mipmapTexture);
else tm.addTexture(textureName, mipmapTexture);
lastTexture = mipmapTexture;
lastTextureId = tm.getTextureID(textureName);
textureIds.put(textureName, lastTextureId);
createdTextures.add(textureName);
Logger.log("mip map texture created " + textureName + " " + width + "x" + height, Logger.MESSAGE);
} else {
// no need to create a new texture so use last one
String textureName = getTextureName(textureId, offset);
textureIds.put(textureName, lastTextureId);
Logger.log("re-used last mipmap texture " + textureName + " " + width + "x" + height, Logger.MESSAGE);
}
} // for offset
processedTextures.set(textureId);
} // if (!processedTextures.get(textureId)
} // for polygonId
}
private String getTextureName(int baseTextureId, int offset) {
return "mipmap/" + baseTextureId + "/" + offset;
}
/**
* Does the actual mipmapping based on the current visibility list.
* Has to be executed after/before rendering/drawing the scene, so it's always
* "one step behind" but if you don't move very fast, this shouldn't matter.
*/
public void mipMap(World world, FrameBuffer buffer) {
if (mipMapData.isEmpty())
return; // no mipMapped objects so return
VisList vl = world.getVisibilityList();
int size = vl.getSize();
int lastObjectId = -1;
ObjectData objectData = null;
changedObjects.clear();
for (int i = 0; i < size; i++) {
vl.getData(i, visListData);
if (lastObjectId != visListData[0]) {
lastObjectId = visListData[0];
objectData = mipMapData.get(visListData[0]);
}
if (objectData != null) {
SimpleVector v0, v1, v2;
int polygonId = visListData[1];
if ((v0 = Interact2D.project3D2D(world.getCamera(),
buffer, objectData.pm.getTransformedVertex(polygonId, 0))) == null)
continue;
if ((v1 = Interact2D.project3D2D(world.getCamera(),
buffer, objectData.pm.getTransformedVertex(polygonId, 1))) == null)
continue;
if ((v2 = Interact2D.project3D2D(world.getCamera(),
buffer, objectData.pm.getTransformedVertex(polygonId, 2))) == null)
continue;
float polygonArea = calculateArea(v0, v1, v2);
float ratio = polygonArea / objectData.textureAreas[polygonId];
int offset = -1; boolean done = false;
while (!done && (++offset < LEVELS - 1)) {
done = ratio > FIRST_LEVEL;
ratio *= 4f;
}
int targetTex = objectData.orgTex[polygonId][offset];
if (targetTex != objectData.curTex[polygonId]) {
changedObjects.set(lastObjectId);
objectData.curTex[polygonId] = targetTex;
objectData.pm.setPolygonTexture(polygonId, targetTex);
}
}
}
for (int i = changedObjects.nextSetBit(0); i >= 0; i = changedObjects.nextSetBit(i+1)) {
world.getObject(i).recreateTextureCoords();
}
}
/** resets all textures to their original states
* i.e. disables mip mapping */
public void reset() {
for (ObjectData objectData : mipMapData.values())
objectData.reset();
}
/**
* useful for cleanup purposes
* (i.e you dont want call TextureManager.flush() for whatever reason)
*/
public void dispose() {
for (String textureName : createdTextures) {
if (tm.containsTexture(textureName))
tm.replaceTexture(textureName, tm.getDummyTexture());
else Logger.log("couldnt find texture " + textureName, Logger.WARNING);
}
}
private float calculateArea(SimpleVector... v) {
float area = 0f;
for (int i = 0; i < v.length; i++) {
int j = (i + 1) % v.length;
area += v[i].x * v[j].y;
area -= v[i].y * v[j].x;
}
area /= 2.0;
return (area < 0) ? -area : area;
}
/** data structure to store polygon data per object */
private class ObjectData {
private final Object3D object3d;
private final PolygonManager pm;
private final int[][] orgTex;
private final int[] curTex;
private final float[] textureAreas;
private ObjectData(Object3D object3d) {
this.object3d = object3d;
pm = object3d.getPolygonManager();
orgTex = new int[pm.getMaxPolygonID()][LEVELS];
curTex = new int[pm.getMaxPolygonID()];
textureAreas = new float[pm.getMaxPolygonID()];
createMipMaps(object3d);
for (int i = 0; i < orgTex.length; i++) {
int textureId = pm.getPolygonTexture(i);
orgTex[i][0] = textureId;
for (int offset = 1; offset< LEVELS; offset++) {
orgTex[i][offset] = textureIds.get(getTextureName(textureId, offset));
}
curTex[i] = orgTex[i][0];
float uvArea = calculateArea(pm.getTextureUV(i, 0), pm.getTextureUV(i, 1), pm.getTextureUV(i, 2));
Texture texture = tm.getTextureByID(textureId);
textureAreas[i] = uvArea * texture.getWidth() * texture.getHeight();
}
}
private void reset() {
boolean changed = false;
for (int polygonId = 0; polygonId < orgTex.length; polygonId++) {
int originalTextureId = orgTex[polygonId][0];
if (originalTextureId != curTex[polygonId]) {
changed = true;
pm.setPolygonTexture(polygonId, originalTextureId);
curTex[polygonId] = originalTextureId;
}
}
if (changed)
object3d.recreateTextureCoords();
}
}
/**
* small helper class to access a texture's texels.
*/
private class TexelGrabber implements ITextureEffect {
private int[] texels = null;
public void init(Texture tex) {}
public void apply(int[] dest, int[] source) {
texels = source;
}
}
}