blob: a7272770ed6dc8301e85494a1a79098fe94ebedb [file] [log] [blame]
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.blender.textures;
import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.texture.Texture;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class is a base class for texture generators.
* @author Marcin Roguski (Kaelthas)
*/
/* package */abstract class TextureGenerator {
private static final Logger LOGGER = Logger.getLogger(TextureGenerator.class.getName());
protected NoiseGenerator noiseGenerator;
public TextureGenerator(NoiseGenerator noiseGenerator) {
this.noiseGenerator = noiseGenerator;
}
/**
* This method generates the texture.
* @param tex
* texture's structure
* @param width
* the width of the result texture
* @param height
* the height of the result texture
* @param depth
* the depth of the texture
* @param blenderContext
* the blender context
* @return newly generated texture
*/
protected abstract Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext);
/**
* This method reads the colorband data from the given texture structure.
*
* @param tex
* the texture structure
* @param blenderContext
* the blender context
* @return read colorband or null if not present
*/
private ColorBand readColorband(Structure tex, BlenderContext blenderContext) {
ColorBand result = null;
int flag = ((Number) tex.getFieldValue("flag")).intValue();
if ((flag & NoiseGenerator.TEX_COLORBAND) != 0) {
Pointer pColorband = (Pointer) tex.getFieldValue("coba");
Structure colorbandStructure;
try {
colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0);
result = new ColorBand(colorbandStructure);
} catch (BlenderFileException e) {
LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage());
}
}
return result;
}
protected float[][] computeColorband(Structure tex, BlenderContext blenderContext) {
ColorBand colorBand = this.readColorband(tex, blenderContext);
float[][] result = null;
if(colorBand!=null) {
result = new float[1001][4];//1001 - amount of possible cursor positions; 4 = [r, g, b, a]
ColorBandData[] dataArray = colorBand.data;
if(dataArray.length==1) {//special case; use only one color for all types of colorband interpolation
for(int i=0;i<result.length;++i) {
result[i][0] = dataArray[0].r;
result[i][1] = dataArray[0].g;
result[i][2] = dataArray[0].b;
result[i][3] = dataArray[0].a;
}
} else {
int currentCursor = 0;
ColorBandData currentData = dataArray[0];
ColorBandData nextData = dataArray[0];
switch(colorBand.ipoType) {
case ColorBand.IPO_LINEAR:
float rDiff = 0, gDiff = 0, bDiff = 0, aDiff = 0, posDiff;
for(int i=0;i<result.length;++i) {
posDiff = i - currentData.pos;
result[i][0] = currentData.r + rDiff * posDiff;
result[i][1] = currentData.g + gDiff * posDiff;
result[i][2] = currentData.b + bDiff * posDiff;
result[i][3] = currentData.a + aDiff * posDiff;
if(nextData.pos==i) {
currentData = dataArray[currentCursor++];
if(currentCursor < dataArray.length) {
nextData = dataArray[currentCursor];
//calculate differences
int d = nextData.pos - currentData.pos;
rDiff = (nextData.r - currentData.r)/d;
gDiff = (nextData.g - currentData.g)/d;
bDiff = (nextData.b - currentData.b)/d;
aDiff = (nextData.a - currentData.a)/d;
} else {
rDiff = gDiff = bDiff = aDiff = 0;
}
}
}
break;
case ColorBand.IPO_BSPLINE:
case ColorBand.IPO_CARDINAL:
Map<Integer, ColorBandData> cbDataMap = new TreeMap<Integer, ColorBandData>();
for(int i=0;i<colorBand.data.length;++i) {
cbDataMap.put(Integer.valueOf(i), colorBand.data[i]);
}
if(colorBand.data[0].pos==0) {
cbDataMap.put(Integer.valueOf(-1), colorBand.data[0]);
} else {
ColorBandData cbData = colorBand.data[0].clone();
cbData.pos = 0;
cbDataMap.put(Integer.valueOf(-1), cbData);
cbDataMap.put(Integer.valueOf(-2), cbData);
}
if(colorBand.data[colorBand.data.length - 1].pos==1000) {
cbDataMap.put(Integer.valueOf(colorBand.data.length), colorBand.data[colorBand.data.length - 1]);
} else {
ColorBandData cbData = colorBand.data[colorBand.data.length - 1].clone();
cbData.pos = 1000;
cbDataMap.put(Integer.valueOf(colorBand.data.length), cbData);
cbDataMap.put(Integer.valueOf(colorBand.data.length + 1), cbData);
}
float[] ipoFactors = new float[4];
float f;
ColorBandData data0 = cbDataMap.get(currentCursor - 2);
ColorBandData data1 = cbDataMap.get(currentCursor - 1);
ColorBandData data2 = cbDataMap.get(currentCursor);
ColorBandData data3 = cbDataMap.get(currentCursor + 1);
for(int i=0;i<result.length;++i) {
if (data2.pos != data1.pos) {
f = (i - data2.pos) / (float)(data1.pos - data2.pos);
} else {
f = 0.0f;
}
f = FastMath.clamp(f, 0.0f, 1.0f);
this.getIpoData(colorBand, f, ipoFactors);
result[i][0] = ipoFactors[3] * data0.r + ipoFactors[2] * data1.r + ipoFactors[1] * data2.r + ipoFactors[0] * data3.r;
result[i][1] = ipoFactors[3] * data0.g + ipoFactors[2] * data1.g + ipoFactors[1] * data2.g + ipoFactors[0] * data3.g;
result[i][2] = ipoFactors[3] * data0.b + ipoFactors[2] * data1.b + ipoFactors[1] * data2.b + ipoFactors[0] * data3.b;
result[i][3] = ipoFactors[3] * data0.a + ipoFactors[2] * data1.a + ipoFactors[1] * data2.a + ipoFactors[0] * data3.a;
result[i][0] = FastMath.clamp(result[i][0], 0.0f, 1.0f);
result[i][1] = FastMath.clamp(result[i][1], 0.0f, 1.0f);
result[i][2] = FastMath.clamp(result[i][2], 0.0f, 1.0f);
result[i][3] = FastMath.clamp(result[i][3], 0.0f, 1.0f);
if(nextData.pos==i) {
++currentCursor;
data0 = cbDataMap.get(currentCursor - 2);
data1 = cbDataMap.get(currentCursor - 1);
data2 = cbDataMap.get(currentCursor);
data3 = cbDataMap.get(currentCursor + 1);
}
}
break;
case ColorBand.IPO_EASE:
float d, a, b, d2;
for(int i=0;i<result.length;++i) {
if(nextData.pos != currentData.pos) {
d = (i - currentData.pos) / (float)(nextData.pos - currentData.pos);
d2 = d * d;
a = 3.0f * d2 - 2.0f * d * d2;
b = 1.0f - a;
} else {
d = a = 0.0f;
b = 1.0f;
}
result[i][0] = b * currentData.r + a * nextData.r;
result[i][1] = b * currentData.g + a * nextData.g;
result[i][2] = b * currentData.b + a * nextData.b;
result[i][3] = b * currentData.a + a * nextData.a;
if(nextData.pos==i) {
currentData = dataArray[currentCursor++];
if(currentCursor < dataArray.length) {
nextData = dataArray[currentCursor];
}
}
}
break;
case ColorBand.IPO_CONSTANT:
for(int i=0;i<result.length;++i) {
result[i][0] = currentData.r;
result[i][1] = currentData.g;
result[i][2] = currentData.b;
result[i][3] = currentData.a;
if(nextData.pos==i) {
currentData = dataArray[currentCursor++];
if(currentCursor < dataArray.length) {
nextData = dataArray[currentCursor];
}
}
}
break;
default:
throw new IllegalStateException("Unknown interpolation type: " + colorBand.ipoType);
}
}
}
return result;
}
/**
* This method returns the data for either B-spline of Cardinal interpolation.
* @param colorBand the color band
* @param d distance factor for the current intensity
* @param ipoFactors table to store the results (size of the table must be at least 4)
*/
private void getIpoData(ColorBand colorBand, float d, float[] ipoFactors) {
float d2 = d * d;
float d3 = d2 * d;
if(colorBand.ipoType==ColorBand.IPO_BSPLINE) {
ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d;
ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f;
ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d;
ipoFactors[3] = 0.71f * d3 - 0.71f * d2;
} else if(colorBand.ipoType==ColorBand.IPO_CARDINAL) {
ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f;
ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f;
ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f;
ipoFactors[3] = 0.16666666f * d3;
} else {
throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!");
}
}
/**
* This method applies brightness and contrast for RGB textures.
* @param tex texture structure
* @param texres
*/
protected void applyBrightnessAndContrast(BrightnessAndContrastData bacd, TexturePixel texres) {
texres.red = (texres.red - 0.5f) * bacd.contrast + bacd.brightness;
if (texres.red < 0.0f) {
texres.red = 0.0f;
}
texres.green =(texres.green - 0.5f) * bacd.contrast + bacd.brightness;
if (texres.green < 0.0f) {
texres.green = 0.0f;
}
texres.blue = (texres.blue - 0.5f) * bacd.contrast + bacd.brightness;
if (texres.blue < 0.0f) {
texres.blue = 0.0f;
}
}
/**
* This method applies brightness and contrast for Luminance textures.
* @param texres
* @param contrast
* @param brightness
*/
protected void applyBrightnessAndContrast(TexturePixel texres, float contrast, float brightness) {
texres.intensity = (texres.intensity - 0.5f) * contrast + brightness;
if (texres.intensity < 0.0f) {
texres.intensity = 0.0f;
} else if (texres.intensity > 1.0f) {
texres.intensity = 1.0f;
}
}
/**
* A class constaining the colorband data.
*
* @author Marcin Roguski (Kaelthas)
*/
protected static class ColorBand {
//interpolation types
public static final int IPO_LINEAR = 0;
public static final int IPO_EASE = 1;
public static final int IPO_BSPLINE = 2;
public static final int IPO_CARDINAL = 3;
public static final int IPO_CONSTANT = 4;
public int cursorsAmount, ipoType;
public ColorBandData[] data;
/**
* Constructor. Loads the data from the given structure.
*
* @param cbdataStructure
* the colorband structure
*/
@SuppressWarnings("unchecked")
public ColorBand(Structure colorbandStructure) {
this.cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue();
this.ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue();
this.data = new ColorBandData[this.cursorsAmount];
DynamicArray<Structure> data = (DynamicArray<Structure>) colorbandStructure.getFieldValue("data");
for (int i = 0; i < this.cursorsAmount; ++i) {
this.data[i] = new ColorBandData(data.get(i));
}
}
}
/**
* Class to store the single colorband cursor data.
*
* @author Marcin Roguski (Kaelthas)
*/
protected static class ColorBandData implements Cloneable {
public final float r, g, b, a;
public int pos;
/**
* Copy constructor.
*/
private ColorBandData(ColorBandData data) {
this.r = data.r;
this.g = data.g;
this.b = data.b;
this.a = data.a;
this.pos = data.pos;
}
/**
* Constructor. Loads the data from the given structure.
*
* @param cbdataStructure
* the structure containing the CBData object
*/
public ColorBandData(Structure cbdataStructure) {
this.r = ((Number) cbdataStructure.getFieldValue("r")).floatValue();
this.g = ((Number) cbdataStructure.getFieldValue("g")).floatValue();
this.b = ((Number) cbdataStructure.getFieldValue("b")).floatValue();
this.a = ((Number) cbdataStructure.getFieldValue("a")).floatValue();
this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f);
}
@Override
public ColorBandData clone() {
try {
return (ColorBandData) super.clone();
} catch (CloneNotSupportedException e) {
return new ColorBandData(this);
}
}
@Override
public String toString() {
return "P: " + this.pos + " [" + this.r+", "+this.g+", "+this.b+", "+this.a+"]";
}
}
/**
* This class contains brightness and contrast data.
* @author Marcin Roguski (Kaelthas)
*/
protected static class BrightnessAndContrastData {
public final float contrast;
public final float brightness;
public final float rFactor;
public final float gFactor;
public final float bFactor;
/**
* Constructor reads the required data from the given structure.
* @param tex texture structure
*/
public BrightnessAndContrastData(Structure tex) {
contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
brightness = ((Number) tex.getFieldValue("bright")).floatValue() - 0.5f;
rFactor = ((Number) tex.getFieldValue("rfac")).floatValue();
gFactor = ((Number) tex.getFieldValue("gfac")).floatValue();
bFactor = ((Number) tex.getFieldValue("bfac")).floatValue();
}
}
}