/*
* MegaMek -
* Copyright (C) 2000,2001,2002,2003,2004,2005,2006 Ben Mazur (bmazur@sev.org)
*
* This file (C) 2008 J�rg Walter <j.walter@syntax-k.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package megamek.client.ui.AWT.boardview3d;
import com.sun.j3d.utils.image.TextureLoader;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
import javax.media.j3d.Appearance;
import javax.media.j3d.Node;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Texture;
import javax.media.j3d.Texture2D;
import megamek.client.ui.AWT.TilesetManager;
import megamek.client.ui.AWT.util.ImageCache;
import megamek.common.Entity;
import megamek.common.IGame;
import megamek.common.IHex;
import megamek.common.preference.IClientPreferences;
import megamek.common.preference.IPreferenceChangeListener;
import megamek.common.preference.PreferenceChangeEvent;
import megamek.common.preference.PreferenceManager;
class TileTextureManager implements IPreferenceChangeListener {
private TilesetManager tileManager;
private Component comp;
private IGame game;
// texture image cache
private static ImageCache<ImageAlpha, Texture2D> textureCache = new ImageCache<ImageAlpha, Texture2D>();
private static final int TEXSIZE = 256;
static final float HEX_ALPHA = 2/3f;
static final float HEX_ALPHA2 = .4f;
public TileTextureManager(Component c, IGame g) throws IOException {
comp = c;
game = g;
tileManager = new TilesetManager(comp);
System.out.println("TileTextureManager: loading images for board"); //$NON-NLS-1$
tileManager.loadNeededImages(game);
PreferenceManager.getClientPreferences().addPreferenceChangeListener(this);
}
public TilesetManager getTilesetManager() {
return tileManager;
}
public Shape3D getModel(Entity entity) {
return getModel(tileManager.imageFor(entity, 0), 1.0f, true);
}
public Shape3D getModel(IHex hex) {
return getModel(tileManager.baseFor(hex), HEX_ALPHA2, false);
}
public List<Shape3D> getModels(IHex hex) {
List<Image> supers = tileManager.supersFor(hex);
List<Shape3D> out = new Vector<Shape3D>();
if (supers != null) {
for (Image img : supers) {
out.add(getModel(img, HEX_ALPHA, false));
}
}
return out;
}
public Shape3D getModel(Image img, float alpha, boolean scale) {
MediaTracker tracker = new MediaTracker(comp);
// Fully load image before continuing
tracker.addImage(img, 0);
do {
try { tracker.waitForID(0); } catch (InterruptedException e) { continue; }
} while (false);
tracker.removeImage(img);
final int w = img.getWidth(null), h = img.getHeight(null);
BufferedImage tmp = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D tgr = tmp.createGraphics();
tgr.drawImage(img, null, null);
tgr.dispose();
tgr = null;
tracker.addImage(tmp, 1);
do {
try { tracker.waitForID(1); } catch (InterruptedException e) { continue; }
} while (false);
tracker.removeImage(tmp);
Shape3D model = new ImageModel(tmp, scale);
Appearance app = new Appearance();
app.setTexture(getTexture(img, false, null, alpha, true));
if (alpha != 1.0) app.setTransparencyAttributes(C.alphaTexture);
model.setAppearance(app);
return model;
}
public Texture2D getTexture(Entity entity) {
return getTexture(tileManager.imageFor(entity, 0), false, Color.GRAY, 1.0f, true);
}
public Texture2D getTexture(IHex hex, float alpha) {
return getTexture(tileManager.baseFor(hex), true, null, alpha, true);
}
public Node getMinefieldSign() {
return new SignModel(getTexture(tileManager.getMinefieldSign(), false, null, 1.0f, false));
}
Shape3D getArtilleryTarget(int type) {
return getModel(tileManager.getArtilleryTarget(type), .5f, false);
}
public Texture2D getTexture(String filename) {
TextureLoader tl = new TextureLoader(filename, TextureLoader.GENERATE_MIPMAP, null);
Texture2D tex = (Texture2D)tl.getTexture();
tex.setMagFilter(Texture.NICEST);
tex.setMinFilter(Texture.NICEST);
return tex;
}
private final Texture2D getTexture(Image base, boolean border, Color col, float alpha, boolean tile) {
if (base == null) return null;
Texture2D tex = textureCache.get(new ImageAlpha(base, alpha));
if (tex == null) {
MediaTracker tracker = new MediaTracker(comp);
// Fully load image before continuing
tracker.addImage(base, 0);
do {
try { tracker.waitForID(0); } catch (InterruptedException e) { continue; }
} while (false);
tracker.removeImage(base);
final int w = base.getWidth(null), h = base.getHeight(null);
BufferedImage tmp = new BufferedImage(w*2, h*2, BufferedImage.TYPE_INT_ARGB);
Graphics2D tgr = tmp.createGraphics();
if (col != null) {
tgr.setColor(col);
tgr.fillRect(0, 0, w*2, h*2);
}
tgr.translate(w/2, h/2);
tgr.drawImage(base, null, null);
if (tile) {
// create an image of a hex with all 6 surrounding hexes to get
// better interpolation at the borders
tgr.translate(0, -h);
tgr.drawImage(base, null, null);
tgr.translate(0, 2*h);
tgr.drawImage(base, null, null);
tgr.translate(-3*w/4, -h/2);
tgr.drawImage(base, null, null);
tgr.translate(0, -h);
tgr.drawImage(base, null, null);
tgr.translate(3*w/2, 0);
tgr.drawImage(base, null, null);
tgr.translate(0, h);
tgr.drawImage(base, null, null);
}
tracker.addImage(tmp, 1);
do {
try { tracker.waitForID(1); } catch (InterruptedException e) { continue; }
} while (false);
tracker.removeImage(tmp);
// TODO: Make this configurable.
// Cut outer pixel border? This removes the distinct tile border present in most tiles.
int border_cut = 1;
// Add our own pixel border? Higher quality (resolution) than the image's tile border.
int border_add = 3;
if (!border) {
border_cut = 0;
border_add = 0;
}
// Scale texture image, cutting border if needed
BufferedImage src = new BufferedImage(TEXSIZE, TEXSIZE, BufferedImage.TYPE_INT_ARGB);
AffineTransform at = new AffineTransform();
at.scale(TEXSIZE/(double)(w-border_cut*2), TEXSIZE/(double)(h-border_cut*2));
at.translate(-w/2-border_cut, -h/2-border_cut);
AffineTransformOp ato = new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC);
ato.filter(tmp, src);
if (border_add > 0) {
// Add border if needed
Graphics2D gr = src.createGraphics();
gr.setColor(Color.DARK_GRAY);
gr.setStroke(new BasicStroke(border_add));
gr.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
int[] xvals = { TEXSIZE/4, 3*TEXSIZE/4-1, TEXSIZE-1, 3*TEXSIZE/4-1, TEXSIZE/4, 0, TEXSIZE/4 };
int[] yvals = { 0, 0, TEXSIZE/2, TEXSIZE-1, TEXSIZE-1, TEXSIZE/2, 0 };
gr.drawPolygon(xvals, yvals, xvals.length);
}
if (alpha < 1.0f) {
WritableRaster wr = src.getAlphaRaster();
for (int y = 0; y < src.getHeight(); y++) {
for (int x = 0; x < src.getWidth(); x++)
wr.setSample(x, y, 0, alpha*wr.getSampleFloat(x, y, 0));
}
}
// Finally create actual texture
TextureLoader tl = new TextureLoader(src, TextureLoader.GENERATE_MIPMAP);
tex = (Texture2D)tl.getTexture();
tex.setMagFilter(Texture.NICEST);
tex.setMinFilter(Texture.NICEST);
if (tileManager.isLoaded()) textureCache.put(new ImageAlpha(base, alpha), tex);
}
return tex;
}
public void preferenceChange(PreferenceChangeEvent e) {
if (e.getName().equals(IClientPreferences.MAP_TILESET)) {
System.out.println("BoardView3D: loading images for board"); //$NON-NLS-1$
tileManager.loadNeededImages(game);
}
}
void hexChanged(IHex hex) {
tileManager.clearHex(hex);
tileManager.waitForHex(hex);
}
}
class ImageAlpha {
Image img;
float alpha;
public ImageAlpha(Image i, float a) { img = i; alpha = a; }
@Override
public boolean equals(Object o) {
if (o == null || o.getClass() != getClass()) return false;
ImageAlpha a = (ImageAlpha)o;
return a.alpha == alpha && a.img.equals(img);
}
@Override
public int hashCode() {
int hash = 5;
hash = 71 * hash + (this.img != null ? this.img.hashCode() : 0);
hash = 71 * hash + Float.floatToIntBits(this.alpha);
return hash;
}
}