/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2011 Andreas Maschke
This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This software 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this software;
if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jwildfire.transform;
import org.jwildfire.base.Property;
import org.jwildfire.base.PropertyCategory;
import org.jwildfire.image.SimpleHDRImage;
import org.jwildfire.image.SimpleImage;
import org.jwildfire.image.WFImage;
import org.jwildfire.swing.Buffer;
import org.jwildfire.swing.ImageBufferComboBoxEditor;
import org.jwildfire.swing.ScaleAspectEditor;
import com.l2fprod.common.beans.editor.ComboBoxPropertyEditor;
public class Bump3DTransformer extends Mesh3DTransformer {
public enum SmoothingMatrix {
MATRIX_3x3, MATRIX_5x5,
}
@Property(category = PropertyCategory.PRIMARY, description = "Image which holds the height information", editorClass = ImageBufferComboBoxEditor.class)
private Buffer heightMap;
@Property(category = PropertyCategory.PRIMARY, description = "Maximum displacement amount")
private double amount = 10.0;
@Property(category = PropertyCategory.PRIMARY, description = "Size of the smoothing matrix (larger -> smoother and slower)", editorClass = SmoothingMatrixEditor.class)
private SmoothingMatrix smoothingMatrix = SmoothingMatrix.MATRIX_3x3;
@Property(category = PropertyCategory.SECONDARY, description = "Left offset of the height map")
private int hLeft = 0;
@Property(category = PropertyCategory.SECONDARY, description = "Top offset of the height map")
private int hTop = 0;
@Property(category = PropertyCategory.SECONDARY, description = "Scaled width of the height map")
private int hWidth = 400;
@Property(category = PropertyCategory.SECONDARY, description = "Scaled height of the height map")
private int hHeight = 400;
@Property(category = PropertyCategory.SECONDARY, description = "How to treat the aspect ration of the height map", editorClass = ScaleAspectEditor.class)
private ScaleAspect aspect = ScaleAspect.IGNORE;
@Property(category = PropertyCategory.SECONDARY, description = "Treat the lowest intensity of the heightmap as zero intensity")
private boolean noGround = true;
@Property(category = PropertyCategory.SECONDARY, description = "Centre the height map")
private boolean hCentre = true;
private double lumMin, lumMax, lumRange;
@Override
protected void transformMesh(Mesh3D pMesh3D, int pImageWidth, int pImageHeight) {
int pCount = pMesh3D.getPCount();
int width = pImageWidth;
int height = pImageHeight;
double x[] = pMesh3D.getX();
double y[] = pMesh3D.getY();
double z[] = pMesh3D.getZ();
WFImage heightMap = this.heightMap.getHDRImage();
if (heightMap != null) {
int hwidth = heightMap.getImageWidth();
int hheight = heightMap.getImageHeight();
if ((hwidth != this.hWidth) || (hheight != this.hHeight)) {
throw new IllegalArgumentException("Heightmap has the wrong size (scaling of HDR images currently not supported)");
}
float lum[] = new float[2];
((SimpleHDRImage) heightMap).getMinMaxLum(lum);
lumMin = lum[0];
lumMax = lum[1];
lumRange = lumMax - lumMin;
}
else {
heightMap = this.heightMap.getImage();
int hwidth = heightMap.getImageWidth();
int hheight = heightMap.getImageHeight();
if ((hwidth != this.hWidth) || (hheight != this.hHeight)) {
SimpleImage scaledHeightMap = ((SimpleImage) heightMap).clone();
ScaleTransformer scaleT = new ScaleTransformer();
scaleT.setAspect(this.aspect);
scaleT.setUnit(ScaleTransformer.Unit.PIXELS);
scaleT.setScaleWidth(this.hWidth);
scaleT.setScaleHeight(this.hHeight);
scaleT.performImageTransformation(scaledHeightMap);
heightMap = scaledHeightMap;
}
}
double amount = 0.0 - this.amount;
int dx = hLeft - width / 2;
int dy = hTop - height / 2;
if (hCentre) {
dx += (width - this.hWidth) / 2;
dy += (height - this.hHeight) / 2;
}
double[][] weights, intArray;
if (this.smoothingMatrix == SmoothingMatrix.MATRIX_3x3) {
int smoothSize = 3;
weights = weights_3x3;
intArray = new double[smoothSize][smoothSize];
}
else {
int smoothSize = 5;
weights = weights_5x5;
intArray = new double[smoothSize][smoothSize];
}
double zmin = 0.0, zmax = 0.0;
for (int i = 0; i < pCount; i++) {
int xx = (int) (x[i] - (double) dx + 0.5);
int yy = (int) (y[i] - (double) dy + 0.5);
if ((xx >= 0) && (xx < this.hWidth) && (yy >= 0) && (yy < this.hHeight)) {
readPixels(heightMap, xx, yy, intArray);
double intensity = getWeightedIntensity(intArray, weights) * amount;
if (intensity < zmin)
zmin = intensity;
else if (intensity > zmax)
zmax = intensity;
z[i] += intensity;
}
}
// Subtract ground
double fam = (zmax - zmin) / 2.0 + zmin;
if ((fam != 0.0) && (noGround)) {
for (int i = 0; i < pCount; i++) {
z[i] -= fam;
}
}
}
private double getWeightedIntensity(double[][] pIntArray, double[][] pWeights) {
double res = 0.0;
int size = pIntArray.length;
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
res += pIntArray[i][j] * pWeights[i][j];
}
}
return res;
}
private void readPixels(WFImage pHeightMap, int pX, int pY, double[][] pIntArray) {
int size = pIntArray.length;
for (int i = 0; i < size; i++) {
int y = pY - size / 2 + i;
for (int j = 0; j < size; j++) {
int x = pX - size / 2 + j;
if (pHeightMap instanceof SimpleHDRImage) {
pIntArray[i][j] = ((((SimpleHDRImage) pHeightMap).getLumIgnoreBounds(x, y)) - lumMin) / lumRange;
}
else {
pIntArray[i][j] = (double) (((SimpleImage) pHeightMap).getRValueIgnoreBounds(x, y)) / 255.0;
}
}
}
}
@Override
public void initDefaultParams(WFImage pImg) {
super.initDefaultParams(pImg);
int width = pImg.getImageWidth();
int height = pImg.getImageHeight();
double rr = Math.sqrt(width * width + height * height);
hLeft = 0;
hTop = 0;
this.hWidth = width;
this.hHeight = height;
aspect = ScaleAspect.IGNORE;
amount = Math.round(rr / 80.0);
noGround = true;
smoothingMatrix = SmoothingMatrix.MATRIX_3x3;
}
public Buffer getHeightMap() {
return heightMap;
}
public void setHeightMap(Buffer heightMap) {
this.heightMap = heightMap;
}
public int getHLeft() {
return hLeft;
}
public void setHLeft(int hLeft) {
this.hLeft = hLeft;
}
public int getHTop() {
return hTop;
}
public void setHTop(int hTop) {
this.hTop = hTop;
}
public int getHWidth() {
return hWidth;
}
public void setHWidth(int hWidth) {
this.hWidth = hWidth;
}
public int getHHeight() {
return hHeight;
}
public void setHHeight(int hHeight) {
this.hHeight = hHeight;
}
public ScaleAspect getAspect() {
return aspect;
}
public void setAspect(ScaleAspect aspect) {
this.aspect = aspect;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public boolean isNoGround() {
return noGround;
}
public void setNoGround(boolean noGround) {
this.noGround = noGround;
}
public boolean isHCentre() {
return hCentre;
}
public void setHCentre(boolean hCentre) {
this.hCentre = hCentre;
}
public static class SmoothingMatrixEditor extends ComboBoxPropertyEditor {
public SmoothingMatrixEditor() {
super();
setAvailableValues(new SmoothingMatrix[] { SmoothingMatrix.MATRIX_3x3,
SmoothingMatrix.MATRIX_5x5 });
}
}
public SmoothingMatrix getSmoothingMatrix() {
return smoothingMatrix;
}
public void setSmoothingMatrix(SmoothingMatrix smoothingMatrix) {
this.smoothingMatrix = smoothingMatrix;
}
private final static double[][] weights_3x3 = { { 0.07, 0.11, 0.07 }, { 0.11, 0.28, 0.11 },
{ 0.07, 0.11, 0.07 } };
private final static double[][] weights_5x5 = { { 0.01, 0.015, 0.035, 0.015, 0.01 },
{ 0.015, 0.05, 0.08, 0.05, 0.015 }, { 0.035, 0.08, 0.18, 0.08, 0.035 },
{ 0.015, 0.05, 0.08, 0.05, 0.015 }, { 0.01, 0.015, 0.035, 0.015, 0.01 } };
}