Feature: added sky loading.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10811 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
experimental
Kae..pl 11 years ago
parent 592303181e
commit eb7e7bbaad
  1. 135
      engine/src/blender/com/jme3/asset/BlenderKey.java
  2. 24
      engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java
  3. 14
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
  4. 184
      engine/src/blender/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java
  5. 94
      engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  6. 77
      engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java
  7. 177
      engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  8. 77
      engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java
  9. 471
      engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageUtils.java
  10. 380
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java
  11. 27
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java
  12. 27
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java

@ -46,9 +46,9 @@ import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.light.AmbientLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.CameraNode;
import com.jme3.scene.LightNode;
import com.jme3.scene.Node;
@ -63,24 +63,24 @@ import com.jme3.texture.Texture;
*/
public class BlenderKey extends ModelKey {
protected static final int DEFAULT_FPS = 25;
protected static final int DEFAULT_FPS = 25;
/**
* FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
* between the frames.
*/
protected int fps = DEFAULT_FPS;
protected int fps = DEFAULT_FPS;
/**
* This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
*/
protected int featuresToLoad = FeaturesToLoad.ALL;
protected int featuresToLoad = FeaturesToLoad.ALL;
/** This variable determines if assets that are not linked to the objects should be loaded. */
protected boolean loadUnlinkedAssets;
/** The root path for all the assets. */
protected String assetRootPath;
/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
protected boolean fixUpAxis = true;
protected boolean fixUpAxis = true;
/** Generated textures resolution (PPU - Pixels Per Unit). */
protected int generatedTexturePPU = 128;
protected int generatedTexturePPU = 128;
/**
* The name of world settings that the importer will use. If not set or specified name does not occur in the file
* then the first world settings in the file will be used.
@ -92,20 +92,25 @@ public class BlenderKey extends ModelKey {
*/
protected Material defaultMaterial;
/** Face cull mode. By default it is disabled. */
protected FaceCullMode faceCullMode = FaceCullMode.Back;
protected FaceCullMode faceCullMode = FaceCullMode.Back;
/**
* Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.
* If set to -1 then the current layer will be loaded.
*/
protected int layersToLoad = -1;
protected int layersToLoad = -1;
/** A variable that toggles the object custom properties loading. */
protected boolean loadObjectProperties = true;
protected boolean loadObjectProperties = true;
/** Maximum texture size. Might be dependant on the graphic card. */
protected int maxTextureSize = -1;
protected int maxTextureSize = -1;
/** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */
protected boolean loadGeneratedTextures;
/** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */
protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
/**
* If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated
* textures will get their proper size.
*/
protected int skyGeneratedTextureSize = 1000;
/**
* Constructor used by serialization mechanisms.
@ -356,6 +361,24 @@ public class BlenderKey extends ModelKey {
this.mipmapGenerationMethod = mipmapGenerationMethod;
}
/**
* @return the size of the generated textures for the sky (used if no flat textures are applied)
*/
public int getSkyGeneratedTextureSize() {
return skyGeneratedTextureSize;
}
/**
* @param skyGeneratedTextureSize
* the size of the generated textures for the sky (used if no flat textures are applied)
*/
public void setSkyGeneratedTextureSize(int skyGeneratedTextureSize) {
if (skyGeneratedTextureSize <= 0) {
throw new IllegalArgumentException("The texture size must be a positive value (the value given as a parameter: " + skyGeneratedTextureSize + ")!");
}
this.skyGeneratedTextureSize = skyGeneratedTextureSize;
}
/**
* This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
* not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
@ -521,6 +544,7 @@ public class BlenderKey extends ModelKey {
int TEXTURES = 0x00000001;
int CAMERAS = 0x00000020;
int LIGHTS = 0x00000010;
int WORLD = 0x00000040;
int ALL = 0xFFFFFFFF;
}
@ -531,21 +555,28 @@ public class BlenderKey extends ModelKey {
public static class LoadingResults extends Spatial {
/** Bitwise mask of features that are to be loaded. */
private final int featuresToLoad;
private final int featuresToLoad;
/** The scenes from the file. */
private List<Node> scenes;
private List<Node> scenes;
/** Objects from all scenes. */
private List<Node> objects;
private List<Node> objects;
/** Materials from all objects. */
private List<Material> materials;
private List<Material> materials;
/** Textures from all objects. */
private List<Texture> textures;
private List<Texture> textures;
/** Animations of all objects. */
private List<AnimationData> animations;
private List<AnimationData> animations;
/** All cameras from the file. */
private List<CameraNode> cameras;
private List<CameraNode> cameras;
/** All lights from the file. */
private List<LightNode> lights;
private List<LightNode> lights;
/** Loaded sky. */
private Spatial sky;
/**
* The background color of the render loaded from the horizon color of the world. If no world is used than the gray color
* is set to default (as in blender editor.
*/
private ColorRGBA backgroundColor = ColorRGBA.Gray;
/**
* Private constructor prevents users to create an instance of this class from outside the
@ -654,7 +685,23 @@ public class BlenderKey extends ModelKey {
}
/**
* This method returns all loaded scenes.
* This method sets the sky of the scene. Only one sky can be set.
* @param sky
* the sky to be set
*/
public void setSky(Spatial sky) {
this.sky = sky;
}
/**
* @param backgroundColor
* the background color
*/
public void setBackgroundColor(ColorRGBA backgroundColor) {
this.backgroundColor = backgroundColor;
}
/**
* @return all loaded scenes
*/
public List<Node> getScenes() {
@ -662,7 +709,6 @@ public class BlenderKey extends ModelKey {
}
/**
* This method returns all loaded objects.
* @return all loaded objects
*/
public List<Node> getObjects() {
@ -670,7 +716,6 @@ public class BlenderKey extends ModelKey {
}
/**
* This method returns all loaded materials.
* @return all loaded materials
*/
public List<Material> getMaterials() {
@ -678,7 +723,6 @@ public class BlenderKey extends ModelKey {
}
/**
* This method returns all loaded textures.
* @return all loaded textures
*/
public List<Texture> getTextures() {
@ -686,7 +730,6 @@ public class BlenderKey extends ModelKey {
}
/**
* This method returns all loaded animations.
* @return all loaded animations
*/
public List<AnimationData> getAnimations() {
@ -694,7 +737,6 @@ public class BlenderKey extends ModelKey {
}
/**
* This method returns all loaded cameras.
* @return all loaded cameras
*/
public List<CameraNode> getCameras() {
@ -702,13 +744,26 @@ public class BlenderKey extends ModelKey {
}
/**
* This method returns all loaded lights.
* @return all loaded lights
*/
public List<LightNode> getLights() {
return lights;
}
/**
* @return the scene's sky
*/
public Spatial getSky() {
return sky;
}
/**
* @return the background color
*/
public ColorRGBA getBackgroundColor() {
return backgroundColor;
}
public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
return 0;
}
@ -744,32 +799,4 @@ public class BlenderKey extends ModelKey {
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
}
}
/**
* The WORLD file block contains various data that could be added to the scene. The contained data includes: ambient
* light.
* @author Marcin Roguski (Kaelthas)
*/
public static class WorldData {
/** The ambient light. */
private AmbientLight ambientLight;
/**
* This method returns the world's ambient light.
* @return the world's ambient light
*/
public AmbientLight getAmbientLight() {
return ambientLight;
}
/**
* This method sets the world's ambient light.
* @param ambientLight
* the world's ambient light
*/
public void setAmbientLight(AmbientLight ambientLight) {
this.ambientLight = ambientLight;
}
}
}

@ -37,9 +37,6 @@ import java.util.logging.Logger;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.asset.BlenderKey.WorldData;
import com.jme3.light.AmbientLight;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.LightNode;
@ -166,25 +163,4 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
// }
// return null;
// }
/**
* This method returns the data read from the WORLD file block. The block contains data that can be stored as
* separate jme features and therefore cannot be returned as a single jME scene feature.
* @param structure
* the structure with WORLD block data
* @return data read from the WORLD block that can be added to the scene
*/
public WorldData toWorldData(Structure structure) {
WorldData result = new WorldData();
// reading ambient light
AmbientLight ambientLight = new AmbientLight();
float ambr = ((Number) structure.getFieldValue("ambr")).floatValue();
float ambg = ((Number) structure.getFieldValue("ambg")).floatValue();
float ambb = ((Number) structure.getFieldValue("ambb")).floatValue();
ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f));
result.setAmbientLight(ambientLight);
return result;
}
}

@ -41,7 +41,6 @@ import com.jme3.asset.AssetInfo;
import com.jme3.asset.BlenderKey;
import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.asset.BlenderKey.LoadingResults;
import com.jme3.asset.BlenderKey.WorldData;
import com.jme3.asset.ModelKey;
import com.jme3.scene.CameraNode;
import com.jme3.scene.LightNode;
@ -56,6 +55,7 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.landscape.LandscapeHelper;
import com.jme3.scene.plugins.blender.lights.LightHelper;
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
import com.jme3.scene.plugins.blender.meshes.MeshHelper;
@ -82,7 +82,6 @@ public class BlenderLoader extends AbstractBlenderLoader {
List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
BlenderKey blenderKey = blenderContext.getBlenderKey();
LoadingResults loadingResults = blenderKey.prepareLoadingResults();
WorldData worldData = null;// a set of data used in different scene aspects
for (FileBlockHeader block : blocks) {
switch (block.getCode()) {
case FileBlockHeader.BLOCK_OB00:// Object
@ -109,14 +108,14 @@ public class BlenderLoader extends AbstractBlenderLoader {
}
break;
case FileBlockHeader.BLOCK_WO00:// World
if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used
if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.WORLD) != 0) {
Structure worldStructure = block.getStructure(blenderContext);
String worldName = worldStructure.getName();
if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
worldData = this.toWorldData(worldStructure);
if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
loadingResults.addLight(worldData.getAmbientLight());
}
LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class);
loadingResults.addLight(landscapeHelper.toAmbientLight(worldStructure));
loadingResults.setSky(landscapeHelper.toSky(worldStructure));
loadingResults.setBackgroundColor(landscapeHelper.toBackgroundColor(worldStructure));
}
}
break;
@ -206,6 +205,7 @@ public class BlenderLoader extends AbstractBlenderLoader {
blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext));
// reading the blocks (dna block is automatically saved in the blender context when found)
FileBlockHeader sceneFileBlock = null;

@ -0,0 +1,184 @@
package com.jme3.scene.plugins.blender.landscape;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.light.AmbientLight;
import com.jme3.light.Light;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.ColorBand;
import com.jme3.scene.plugins.blender.textures.CombinedTexture;
import com.jme3.scene.plugins.blender.textures.ImageUtils;
import com.jme3.scene.plugins.blender.textures.TextureHelper;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.TextureCubeMap;
import com.jme3.util.SkyFactory;
/**
* The class that allows to load the following: <li>the ambient light of the scene <li>the sky of the scene (with or without texture)
*
* @author Marcin Roguski (Kaelthas)
*/
public class LandscapeHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(LandscapeHelper.class.getName());
private static final int SKYTYPE_BLEND = 1;
private static final int SKYTYPE_REAL = 2;
private static final int SKYTYPE_PAPER = 4;
public LandscapeHelper(String blenderVersion, BlenderContext blenderContext) {
super(blenderVersion, blenderContext);
}
/**
* Loads scene ambient light.
* @param worldStructure
* the world's blender structure
* @return the scene's ambient light
*/
public Light toAmbientLight(Structure worldStructure) {
LOGGER.fine("Loading ambient light.");
AmbientLight ambientLight = new AmbientLight();
float ambr = ((Number) worldStructure.getFieldValue("ambr")).floatValue();
float ambg = ((Number) worldStructure.getFieldValue("ambg")).floatValue();
float ambb = ((Number) worldStructure.getFieldValue("ambb")).floatValue();
ColorRGBA ambientLightColor = new ColorRGBA(ambr, ambg, ambb, 0.0f);
ambientLight.setColor(ambientLightColor);
LOGGER.log(Level.FINE, "Loaded ambient light: {0}.", ambientLightColor);
return ambientLight;
}
/**
* Loads the background color.
* @param worldStructure
* the world's structure
* @return the horizon color of the world which is used as a background color.
*/
public ColorRGBA toBackgroundColor(Structure worldStructure) {
float horr = ((Number) worldStructure.getFieldValue("horr")).floatValue();
float horg = ((Number) worldStructure.getFieldValue("horg")).floatValue();
float horb = ((Number) worldStructure.getFieldValue("horb")).floatValue();
return new ColorRGBA(horr, horg, horb, 1);
}
/**
* Loads scene's sky. Sky can be plain or textured.
* If no sky type is selected in blender then no sky is loaded.
* @param worldStructure
* the world's structure
* @return the scene's sky
* @throws BlenderFileException
* blender exception is thrown when problems with blender file occur
*/
public Spatial toSky(Structure worldStructure) throws BlenderFileException {
int skytype = ((Number) worldStructure.getFieldValue("skytype")).intValue();
if (skytype == 0) {
return null;
}
LOGGER.fine("Loading sky.");
ColorRGBA horizontalColor = this.toBackgroundColor(worldStructure);
float zenr = ((Number) worldStructure.getFieldValue("zenr")).floatValue();
float zeng = ((Number) worldStructure.getFieldValue("zeng")).floatValue();
float zenb = ((Number) worldStructure.getFieldValue("zenb")).floatValue();
ColorRGBA zenithColor = new ColorRGBA(zenr, zeng, zenb, 1);
// jutr for this case load generated textures wheather user had set it or not because those might be needed to properly load the sky
boolean loadGeneratedTextures = blenderContext.getBlenderKey().isLoadGeneratedTextures();
blenderContext.getBlenderKey().setLoadGeneratedTextures(true);
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
Map<Number, CombinedTexture> loadedTextures = null;
try {
loadedTextures = textureHelper.readTextureData(worldStructure, new float[] { horizontalColor.r, horizontalColor.g, horizontalColor.b, horizontalColor.a }, true);
} finally {
blenderContext.getBlenderKey().setLoadGeneratedTextures(loadGeneratedTextures);
}
TextureCubeMap texture = null;
if (loadedTextures != null && loadedTextures.size() > 0) {
if (loadedTextures.size() > 1) {
throw new IllegalStateException("There should be only one combined texture for sky!");
}
CombinedTexture combinedTexture = loadedTextures.get(1);
texture = combinedTexture.generateSkyTexture(horizontalColor, zenithColor, blenderContext);
} else {
LOGGER.fine("Preparing colors for colorband.");
int colorbandType = ColorBand.IPO_CARDINAL;
List<ColorRGBA> colorbandColors = new ArrayList<ColorRGBA>(3);
colorbandColors.add(horizontalColor);
if ((skytype & SKYTYPE_BLEND) != 0) {
if ((skytype & SKYTYPE_PAPER) != 0) {
colorbandType = ColorBand.IPO_LINEAR;
}
if ((skytype & SKYTYPE_REAL) != 0) {
colorbandColors.add(0, zenithColor);
}
colorbandColors.add(zenithColor);
}
int size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize();
List<Integer> positions = new ArrayList<Integer>(colorbandColors.size());
positions.add(0);
if (colorbandColors.size() == 2) {
positions.add(size - 1);
} else if (colorbandColors.size() == 3) {
positions.add(size / 2);
positions.add(size - 1);
}
LOGGER.fine("Generating sky texture.");
float[][] values = new ColorBand(colorbandType, colorbandColors, positions, size).computeValues();
Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6);
PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
TexturePixel pixel = new TexturePixel();
LOGGER.fine("Creating side textures.");
int[] sideImagesIndexes = new int[] { 0, 1, 4, 5 };
for (int i : sideImagesIndexes) {
for (int y = 0; y < size; ++y) {
pixel.red = values[y][0];
pixel.green = values[y][1];
pixel.blue = values[y][2];
for (int x = 0; x < size; ++x) {
pixelIO.write(image, i, pixel, x, y);
}
}
}
LOGGER.fine("Creating top texture.");
pixelIO.read(image, 0, pixel, 0, image.getHeight() - 1);
for (int y = 0; y < size; ++y) {
for (int x = 0; x < size; ++x) {
pixelIO.write(image, 3, pixel, x, y);
}
}
texture = new TextureCubeMap(image);
}
LOGGER.fine("Sky texture created. Creating sky.");
return SkyFactory.createSky(blenderContext.getAssetManager(), texture, false);
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true;
}
}

@ -1,7 +1,5 @@
package com.jme3.scene.plugins.blender.materials;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -21,15 +19,11 @@ import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.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.scene.plugins.blender.materials.MaterialHelper.DiffuseShader;
import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader;
import com.jme3.scene.plugins.blender.textures.CombinedTexture;
import com.jme3.scene.plugins.blender.textures.TextureHelper;
import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;
@ -63,7 +57,6 @@ public final class MaterialContext {
/* package */final boolean vTangent;
/* package */FaceCullMode faceCullMode;
@SuppressWarnings("unchecked")
/* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
name = structure.getName();
@ -101,56 +94,8 @@ public final class MaterialContext {
ambientColor = new ColorRGBA(r, g, b, alpha);
}
DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue();
List<TextureData> texturesList = new ArrayList<TextureData>();
for (int i = 0; i < mtexsArray.getTotalSize(); ++i) {
Pointer p = mtexsArray.get(i);
if (p.isNotNull() && (separatedTextures & 1 << i) == 0) {
TextureData textureData = new TextureData();
textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0);
textureData.uvCoordinatesType = ((Number) textureData.mtex.getFieldValue("texco")).intValue();
textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue();
textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString();
if(textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) {
textureData.uvCoordinatesName = null;
}
Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex");
if (pTex.isNotNull()) {
Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0);
textureData.textureStructure = tex;
texturesList.add(textureData);
}
}
}
// loading the textures and merging them
Map<Number, List<TextureData>> textureDataMap = this.sortAndFilterTextures(texturesList);
loadedTextures = new HashMap<Number, CombinedTexture>();
float[] diffuseColorArray = new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a };
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
for (Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) {
if (entry.getValue().size() > 0) {
CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue());
for (TextureData textureData : entry.getValue()) {
int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue();
boolean negateTexture = (texflag & 0x04) != 0;
Texture texture = textureHelper.getTexture(textureData.textureStructure, textureData.mtex, blenderContext);
if (texture != null) {
int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue();
float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() };
float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue();
TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac);
combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType,
textureData.textureStructure, textureData.uvCoordinatesName, blenderContext);
}
}
if (combinedTexture.getTexturesCount() > 0) {
loadedTextures.put(entry.getKey(), combinedTexture);
}
}
}
loadedTextures = textureHelper.readTextureData(structure, new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }, false);
// veryfying if the transparency is present
// (in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when
@ -301,34 +246,6 @@ public final class MaterialContext {
return false;
}
/**
* This method sorts the textures by their mapping type. In each group only
* textures of one type are put (either two- or three-dimensional). If the
* mapping type is MTEX_COL then if the texture has no alpha channel then
* all textures before it are discarded and will not be loaded and merged
* because texture with no alpha will cover them anyway.
*
* @return a map with sorted and filtered textures
*/
private Map<Number, List<TextureData>> sortAndFilterTextures(List<TextureData> textures) {
int[] mappings = new int[] { MTEX_COL, MTEX_NOR, MTEX_EMIT, MTEX_SPEC, MTEX_ALPHA, MTEX_AMB };
Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
for (TextureData data : textures) {
Number mapto = (Number) data.mtex.getFieldValue("mapto");
for (int i = 0; i < mappings.length; ++i) {
if ((mappings[i] & mapto.intValue()) != 0) {
List<TextureData> datas = result.get(mappings[i]);
if (datas == null) {
datas = new ArrayList<TextureData>();
result.put(mappings[i], datas);
}
datas.add(data);
}
}
}
return result;
}
/**
* This method sets the face cull mode.
* @param faceCullMode
@ -393,13 +310,4 @@ public final class MaterialContext {
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
return new ColorRGBA(r, g, b, alpha);
}
private static class TextureData {
Structure mtex;
Structure textureStructure;
int uvCoordinatesType;
int projectionType;
/** The name of the user's UV coordinates that are used for this texture. */
String uvCoordinatesName;
}
}

@ -31,6 +31,7 @@
*/
package com.jme3.scene.plugins.blender.textures;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
@ -38,6 +39,7 @@ 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 java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
@ -59,8 +61,46 @@ public class ColorBand {
public static final int IPO_CONSTANT = 4;
private int cursorsAmount, ipoType;
/** The default amount of possible cursor positions. */
private int resultSize = 1001;
private ColorBandData[] data;
/**
* A constructor used to instantiate color band by hand instead of reading it from the blend file.
* @param ipoType
* the interpolation type
* @param colors
* the colorband colors
* @param positions
* the positions for colors' cursors
* @param resultSize
* the size of the result table
*/
public ColorBand(int ipoType, List<ColorRGBA> colors, List<Integer> positions, int resultSize) {
if (colors == null || colors.size() < 1) {
throw new IllegalArgumentException("The amount of colorband's colors must be at least 1.");
}
if (ipoType < IPO_LINEAR || ipoType > IPO_CONSTANT) {
throw new IllegalArgumentException("Unknown colorband interpolation type: " + ipoType);
}
if (positions == null || positions.size() != colors.size()) {
throw new IllegalArgumentException("The size of positions and colors list should be equal!");
}
for (Integer position : positions) {
if (position.intValue() < 0 || position.intValue() >= resultSize) {
throw new IllegalArgumentException("Invalid position value: " + position + "! Should be from range: [0, " + resultSize + "]!");
}
}
cursorsAmount = colors.size();
this.ipoType = ipoType;
this.resultSize = resultSize;
data = new ColorBandData[this.cursorsAmount];
for (int i = 0; i < cursorsAmount; ++i) {
data[i] = new ColorBandData(colors.get(i), positions.get(i));
}
}
/**
* Constructor. Loads the data from the given structure.
* @param tex
@ -113,11 +153,8 @@ public class ColorBand {
public float[][] computeValues() {
float[][] result = null;
if (data != null) {
result = new float[1001][4];// 1001 - amount of possible cursor
// positions; 4 = [r, g, b, a]
if (data.length == 1) {// special case; use only one color for all
// types of colorband interpolation
result = new float[resultSize][4];// resultSize - amount of possible cursor positions; 4 = [r, g, b, a]
if (data.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] = data[0].r;
result[i][1] = data[0].g;
@ -167,7 +204,7 @@ public class ColorBand {
if (data[0].pos == 0) {
cbDataMap.put(Integer.valueOf(-1), data[0]);
} else {
ColorBandData cbData = data[0].clone();
ColorBandData cbData = new ColorBandData(data[0]);
cbData.pos = 0;
cbDataMap.put(Integer.valueOf(-1), cbData);
cbDataMap.put(Integer.valueOf(-2), cbData);
@ -176,7 +213,7 @@ public class ColorBand {
if (data[data.length - 1].pos == 1000) {
cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]);
} else {
ColorBandData cbData = data[data.length - 1].clone();
ColorBandData cbData = new ColorBandData(data[data.length - 1]);
cbData.pos = 1000;
cbDataMap.put(Integer.valueOf(data.length), cbData);
cbDataMap.put(Integer.valueOf(data.length + 1), cbData);
@ -307,7 +344,7 @@ public class ColorBand {
*
* @author Marcin Roguski (Kaelthas)
*/
private static class ColorBandData implements Cloneable {
private static class ColorBandData {
public final float r, g, b, a;
public int pos;
@ -316,6 +353,21 @@ public class ColorBand {
a = 1;
}
/**
* Constructor that stores the color and position of the cursor.
* @param color
* the cursor's color
* @param pos
* the cursor's position
*/
public ColorBandData(ColorRGBA color, int pos) {
r = color.r;
g = color.g;
b = color.b;
a = color.a;
this.pos = pos;
}
/**
* Copy constructor.
*/
@ -341,15 +393,6 @@ public class ColorBand {
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 + "]";

@ -3,13 +3,16 @@ package com.jme3.scene.plugins.blender.textures;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import jme3tools.converters.ImageToAwt;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
@ -30,6 +33,8 @@ import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.util.BufferUtils;
/**
* This class represents a texture that is defined for the material. It can be
@ -43,6 +48,11 @@ public class CombinedTexture {
/** The mapping type of the texture. Defined bu MaterialContext.MTEX_COL, MTEX_NOR etc. */
private final int mappingType;
/**
* If set to true then if a texture without alpha is added then all textures below are discarded because
* the new one will cover them anyway. If set to false then all textures are stored.
*/
private boolean discardCoveredTextures;
/** The data for each of the textures. */
private List<TextureData> textureDatas = new ArrayList<TextureData>();
/** The result texture. */
@ -55,9 +65,13 @@ public class CombinedTexture {
*
* @param mappingType
* texture mapping type
* @param discardCoveredTextures
* if set to true then if a texture without alpha is added then all textures below are discarded because
* the new one will cover them anyway, if set to false then all textures are stored
*/
public CombinedTexture(int mappingType) {
public CombinedTexture(int mappingType, boolean discardCoveredTextures) {
this.mappingType = mappingType;
this.discardCoveredTextures = discardCoveredTextures;
}
/**
@ -75,7 +89,7 @@ public class CombinedTexture {
* @param textureStructure
* the texture sructure
* @param uvCoordinatesName
* the name of the used user's UV coordinates for this texture
* the name of the used user's UV coordinates for this texture
* @param blenderContext
* the blender context
*/
@ -93,7 +107,7 @@ public class CombinedTexture {
textureData.textureStructure = textureStructure;
textureData.uvCoordinatesName = uvCoordinatesName;
if (textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) {
if (discardCoveredTextures && textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) {
textureDatas.clear();// clear previous textures, they will be covered anyway
}
textureDatas.add(textureData);
@ -120,7 +134,6 @@ public class CombinedTexture {
*/
@SuppressWarnings("unchecked")
public void flatten(Geometry geometry, Long geometriesOMA, LinkedHashMap<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
Mesh mesh = geometry.getMesh();
Texture previousTexture = null;
UVCoordinatesType masterUVCoordinatesType = null;
@ -128,7 +141,7 @@ public class CombinedTexture {
for (TextureData textureData : textureDatas) {
// decompress compressed textures (all will be merged into one texture anyway)
if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) {
textureData.texture.setImage(textureHelper.decompress(textureData.texture.getImage()));
textureData.texture.setImage(ImageUtils.decompress(textureData.texture.getImage()));
textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender);
}
@ -139,8 +152,8 @@ public class CombinedTexture {
resultTexture = textureData.texture;
if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
if(textureData.uvCoordinatesName == null) {
resultUVS = userDefinedUVCoordinates.values().iterator().next();//get the first UV available
if (textureData.uvCoordinatesName == null) {
resultUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available
} else {
resultUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
}
@ -167,11 +180,9 @@ public class CombinedTexture {
triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext);
resultTexture = previousTexture = triangulatedTexture;
} else if (textureData.texture instanceof Texture2D) {
if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName,
textureData.uvCoordinatesType, textureData.uvCoordinatesName) &&
resultTexture instanceof Texture2D) {
if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName, textureData.uvCoordinatesType, textureData.uvCoordinatesName) && resultTexture instanceof Texture2D) {
this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight());
this.merge((Texture2D) resultTexture, (Texture2D) textureData.texture);
ImageUtils.merge(resultTexture.getImage(), textureData.texture.getImage());
previousTexture = resultTexture;
} else {
if (!(resultTexture instanceof TriangulatedTexture)) {
@ -181,8 +192,8 @@ public class CombinedTexture {
// first triangulate the current texture
List<Vector2f> textureUVS = null;
if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
if(textureData.uvCoordinatesName == null) {
textureUVS = userDefinedUVCoordinates.values().iterator().next();//get the first UV available
if (textureData.uvCoordinatesName == null) {
textureUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available
} else {
textureUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
}
@ -193,7 +204,10 @@ public class CombinedTexture {
TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext);
// then move the texture to different UV's
triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext);
((TriangulatedTexture) resultTexture).merge(triangulatedTexture);
// merge triangulated textures
for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) {
ImageUtils.merge(((TriangulatedTexture) resultTexture).getFaceTextureElement(i).image, triangulatedTexture.getImage());
}
}
}
}
@ -203,7 +217,7 @@ public class CombinedTexture {
if (mappingType == MaterialContext.MTEX_NOR) {
for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) {
TriangleTextureElement triangleTextureElement = ((TriangulatedTexture) resultTexture).getFaceTextureElement(i);
triangleTextureElement.image = textureHelper.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor
triangleTextureElement.image = ImageUtils.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor
}
}
resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS();
@ -218,22 +232,102 @@ public class CombinedTexture {
resultTexture.setMinFilter(MinFilter.NearestNoMipMaps);
}
/**
* Generates a texture that will be used by the sky spatial.
* The result texture has 6 layers. Every image in each layer has equal size and its shape is a square.
* The size of each image is the maximum size (width or height) of the textures given.
* The default sky generated texture size is used (this value is set in the BlenderKey) if no picture textures
* are present or their sizes is lower than the generated texture size.
* The textures of lower sizes are properly scaled.
* All the textures are mixed into one and put as layers in the result texture.
*
* @param horizontalColor
* the horizon color
* @param zenithColor
* the zenith color
* @param blenderContext
* the blender context
* @return texture for the sky
*/
public TextureCubeMap generateSkyTexture(ColorRGBA horizontalColor, ColorRGBA zenithColor, BlenderContext blenderContext) {
LOGGER.log(Level.FINE, "Preparing sky texture from {0} applied textures.", textureDatas.size());
LOGGER.fine("Computing the texture size.");
int size = -1;
for (TextureData textureData : textureDatas) {
if (textureData.texture instanceof Texture2D) {
size = Math.max(textureData.texture.getImage().getWidth(), size);
size = Math.max(textureData.texture.getImage().getHeight(), size);
}
}
if (size < 0) {
size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize();
}
LOGGER.log(Level.FINE, "The sky texture size will be: {0}x{0}.", size);
TextureCubeMap result = null;
for (TextureData textureData : textureDatas) {
TextureCubeMap texture = null;
if (textureData.texture instanceof GeneratedTexture) {
texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor);
} else {
// first create a grayscale version of the image
Image image = textureData.texture.getImage();
if (image.getWidth() != image.getHeight() || image.getWidth() != size) {
image = ImageUtils.resizeTo(image, size, size);
}
Image grayscaleImage = ImageUtils.convertToGrayscaleTexture(image);
// add the sky colors to the image
PixelInputOutput sourcePixelIO = PixelIOFactory.getPixelIO(grayscaleImage.getFormat());
PixelInputOutput targetPixelIO = PixelIOFactory.getPixelIO(image.getFormat());
TexturePixel texturePixel = new TexturePixel();
for (int x = 0; x < image.getWidth(); ++x) {
for (int y = 0; y < image.getHeight(); ++y) {
sourcePixelIO.read(grayscaleImage, 0, texturePixel, x, y);
texturePixel.intensity = texturePixel.red;// no matter which factor we use here, in grayscale they are all equal
ImageUtils.color(texturePixel, horizontalColor, zenithColor);
targetPixelIO.write(image, 0, texturePixel, x, y);
}
}
// create the cubemap texture from the coloured image
ByteBuffer sourceData = image.getData(0);
ArrayList<ByteBuffer> data = new ArrayList<ByteBuffer>(6);
for (int i = 0; i < 6; ++i) {
data.add(BufferUtils.clone(sourceData));
}
texture = new TextureCubeMap(new Image(image.getFormat(), image.getWidth(), image.getHeight(), 6, data));
}
if (result == null) {
result = texture;
} else {
ImageUtils.mix(result.getImage(), texture.getImage());
}
}
return result;
}
/**
* The method checks if the texture UV coordinates match.
* It the types are equal and different then UVCoordinatesType.TEXCO_UV then we consider them a match.
* If they are both UVCoordinatesType.TEXCO_UV then they match only when their UV sets names are equal.
* In other cases they are considered NOT a match.
* @param type1 the UV coord type
* @param uvSetName1 the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
* @param type2 the UV coord type
* @param uvSetName2 the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
* @param type1
* the UV coord type
* @param uvSetName1
* the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
* @param type2
* the UV coord type
* @param uvSetName2
* the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
* @return <b>true</b> if the types match and <b>false</b> otherwise
*/
private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1,
UVCoordinatesType type2, String uvSetName2) {
if(type1 == type2) {
if(type1 == UVCoordinatesType.TEXCO_UV) {
if(uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) {
private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1, UVCoordinatesType type2, String uvSetName2) {
if (type1 == type2) {
if (type1 == UVCoordinatesType.TEXCO_UV) {
if (uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) {
return true;
}
} else {
@ -299,39 +393,6 @@ public class CombinedTexture {
return false;
}
/**
* This method merges two given textures. The result is stored in the
* 'target' texture.
*
* @param target
* the target texture
* @param source
* the source texture
*/
private void merge(Texture2D target, Texture2D source) {
if (target.getImage().getDepth() != source.getImage().getDepth()) {
throw new IllegalArgumentException("Cannot merge images with different depths!");
}
Image sourceImage = source.getImage();
Image targetImage = target.getImage();
PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
TexturePixel sourcePixel = new TexturePixel();
TexturePixel targetPixel = new TexturePixel();
int depth = target.getImage().getDepth() == 0 ? 1 : target.getImage().getDepth();
for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
for (int x = 0; x < sourceImage.getWidth(); ++x) {
for (int y = 0; y < sourceImage.getHeight(); ++y) {
sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y);
targetIO.read(targetImage, layerIndex, targetPixel, x, y);
targetPixel.merge(sourcePixel);
targetIO.write(targetImage, layerIndex, targetPixel, x, y);
}
}
}
}
/**
* This method determines if the given texture has no alpha channel.
*
@ -460,6 +521,6 @@ public class CombinedTexture {
/** The texture sructure. */
public Structure textureStructure;
/** The name of the user's UV coordinates that are used for this texture. */
public String uvCoordinatesName;
public String uvCoordinatesName;
}
}

@ -1,6 +1,13 @@
package com.jme3.scene.plugins.blender.textures;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import com.jme3.bounding.BoundingBox;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
@ -10,12 +17,12 @@ import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement;
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
import com.jme3.scene.plugins.blender.textures.generating.TextureGenerator;
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import com.jme3.texture.TextureCubeMap;
/**
* The generated texture loaded from blender file. The texture is not generated
@ -25,6 +32,13 @@ import java.util.TreeSet;
* @author Marcin Roguski (Kaelthas)
*/
/* package */class GeneratedTexture extends Texture {
private static final int POSITIVE_X = 0;
private static final int NEGATIVE_X = 1;
// private static final int POSITIVE_Y = 2;
private static final int NEGATIVE_Y = 3;
private static final int POSITIVE_Z = 4;
private static final int NEGATIVE_Z = 5;
// flag values
public static final int TEX_COLORBAND = 1;
public static final int TEX_FLIPBLEND = 2;
@ -115,6 +129,61 @@ import java.util.TreeSet;
return new TriangulatedTexture(triangleTextureElements, blenderContext);
}
/**
* Creates a texture for the sky. The result texture has 6 layers.
* @param size
* the size of the texture (width and height are equal)
* @param horizontalColor
* the horizon color
* @param zenithColor
* the zenith color
* @return the sky texture
*/
public TextureCubeMap generateSkyTexture(int size, ColorRGBA horizontalColor, ColorRGBA zenithColor) {
Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6);
PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
TexturePixel pixel = new TexturePixel();
float delta = 1 / (float) (size - 1);
float sideV, sideS = 1, forwardU = 1, forwardV, upS;
long time = System.currentTimeMillis();
for (int x = 0; x < size; ++x) {
sideV = 1;
forwardV = 1;
upS = 0;
for (int y = 0; y < size; ++y) {
textureGenerator.getPixel(pixel, 1, sideV, sideS);
pixelIO.write(image, NEGATIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// right
textureGenerator.getPixel(pixel, 0, sideV, 1 - sideS);
pixelIO.write(image, POSITIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// left
textureGenerator.getPixel(pixel, forwardU, forwardV, 0);
pixelIO.write(image, POSITIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// front
textureGenerator.getPixel(pixel, 1 - forwardU, forwardV, 1);
pixelIO.write(image, NEGATIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// back
textureGenerator.getPixel(pixel, forwardU, 0, upS);
pixelIO.write(image, NEGATIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// top
// textureGenerator.getPixel(pixel, forwardU, 1, upS);
// pixelIO.write(image, POSITIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);//bottom
sideV = FastMath.clamp(sideV - delta, 0, 1);
forwardV = FastMath.clamp(forwardV - delta, 0, 1);
upS = FastMath.clamp(upS + delta, 0, 1);
}
sideS = FastMath.clamp(sideS - delta, 0, 1);
forwardU = FastMath.clamp(forwardU - delta, 0, 1);
}
System.out.println(System.currentTimeMillis() - time);
return new TextureCubeMap(image);
}
@Override
public void setWrap(WrapAxis axis, WrapMode mode) {
}

@ -0,0 +1,471 @@
package com.jme3.scene.plugins.blender.textures;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import jme3tools.converters.ImageToAwt;
import jme3tools.converters.RGB565;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.util.BufferUtils;
/**
* This utility class has the methods that deal with images.
*
* @author Marcin Roguski (Kaelthas)
*/
public final class ImageUtils {
/**
* Creates an image of the given size and depth.
* @param format
* the image format
* @param width
* the image width
* @param height
* the image height
* @param depth
* the image depth
* @return the new image instance
*/
public static Image createEmptyImage(Format format, int width, int height, int depth) {
int bufferSize = width * height * (format.getBitsPerPixel() >> 3);
if (depth < 2) {
return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize));
}
ArrayList<ByteBuffer> data = new ArrayList<ByteBuffer>(depth);
for (int i = 0; i < depth; ++i) {
data.add(BufferUtils.createByteBuffer(bufferSize));
}
return new Image(Format.RGB8, width, height, depth, data);
}
/**
* The method sets a color for the given pixel by merging the two given colors.
* The lowIntensityColor will be most visible when the pixel has low intensity.
* The highIntensityColor will be most visible when the pixel has high intensity.
*
* @param pixel
* the pixel that will have the colors altered
* @param lowIntensityColor
* the low intensity color
* @param highIntensityColor
* the high intensity color
* @return the altered pixel (the same instance)
*/
public static TexturePixel color(TexturePixel pixel, ColorRGBA lowIntensityColor, ColorRGBA highIntensityColor) {
float intensity = pixel.intensity;
pixel.fromColor(lowIntensityColor);
pixel.mult(1 - pixel.intensity);
pixel.add(highIntensityColor.mult(intensity));
return pixel;
}
/**
* This method merges two given images. The result is stored in the
* 'target' image.
*
* @param targetImage
* the target image
* @param sourceImage
* the source image
*/
public static void merge(Image targetImage, Image sourceImage) {
if (sourceImage.getDepth() != targetImage.getDepth()) {
throw new IllegalArgumentException("The given images should have the same depth to merge them!");
}
if (sourceImage.getWidth() != targetImage.getWidth()) {
throw new IllegalArgumentException("The given images should have the same width to merge them!");
}
if (sourceImage.getHeight() != targetImage.getHeight()) {
throw new IllegalArgumentException("The given images should have the same height to merge them!");
}
PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
TexturePixel sourcePixel = new TexturePixel();
TexturePixel targetPixel = new TexturePixel();
int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth();
for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
for (int x = 0; x < sourceImage.getWidth(); ++x) {
for (int y = 0; y < sourceImage.getHeight(); ++y) {
sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y);
targetIO.read(targetImage, layerIndex, targetPixel, x, y);
targetPixel.merge(sourcePixel);
targetIO.write(targetImage, layerIndex, targetPixel, x, y);
}
}
}
}
/**
* This method merges two given images. The result is stored in the
* 'target' image.
*
* @param targetImage
* the target image
* @param sourceImage
* the source image
*/
public static void mix(Image targetImage, Image sourceImage) {
if (sourceImage.getDepth() != targetImage.getDepth()) {
throw new IllegalArgumentException("The given images should have the same depth to merge them!");
}
if (sourceImage.getWidth() != targetImage.getWidth()) {
throw new IllegalArgumentException("The given images should have the same width to merge them!");
}
if (sourceImage.getHeight() != targetImage.getHeight()) {
throw new IllegalArgumentException("The given images should have the same height to merge them!");
}
PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
TexturePixel sourcePixel = new TexturePixel();
TexturePixel targetPixel = new TexturePixel();
int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth();
for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
for (int x = 0; x < sourceImage.getWidth(); ++x) {
for (int y = 0; y < sourceImage.getHeight(); ++y) {
sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y);
targetIO.read(targetImage, layerIndex, targetPixel, x, y);
targetPixel.mix(sourcePixel);
targetIO.write(targetImage, layerIndex, targetPixel, x, y);
}
}
}
}
/**
* Resizes the image to the given width and height.
* @param source
* the source image (this remains untouched, the new image instance is created)
* @param width
* the target image width
* @param height
* the target image height
* @return the resized image
*/
public static Image resizeTo(Image source, int width, int height) {
BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
double scaleX = width / (double) sourceImage.getWidth();
double scaleY = height / (double) sourceImage.getHeight();
AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR);
BufferedImage scaledImage = bilinearScaleOp.filter(sourceImage, new BufferedImage(width, height, sourceImage.getType()));
return ImageUtils.toJmeImage(scaledImage, source.getFormat());
}
/**
* This method converts the given texture into normal-map texture.
*
* @param source
* the source texture
* @param strengthFactor
* the normal strength factor
* @return normal-map texture
*/
public static Image convertToNormalMapTexture(Image source, float strengthFactor) {
BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
gscale.filter(sourceImage, heightMap);
Vector3f S = new Vector3f();
Vector3f T = new Vector3f();
Vector3f N = new Vector3f();
for (int x = 0; x < bumpMap.getWidth(); ++x) {
for (int y = 0; y < bumpMap.getHeight(); ++y) {
// generating bump pixel
S.x = 1;
S.y = 0;
S.z = strengthFactor * ImageUtils.getHeight(heightMap, x + 1, y) - strengthFactor * ImageUtils.getHeight(heightMap, x - 1, y);
T.x = 0;
T.y = 1;
T.z = strengthFactor * ImageUtils.getHeight(heightMap, x, y + 1) - strengthFactor * ImageUtils.getHeight(heightMap, x, y - 1);
float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1);
N.x = -S.z;
N.y = -T.z;
N.z = 1;
N.divideLocal(den);
// setting thge pixel in the result image
bumpMap.setRGB(x, y, ImageUtils.vectorToColor(N.x, N.y, N.z));
}
}
return ImageUtils.toJmeImage(bumpMap, source.getFormat());
}
/**
* This method converts the given texture into black and whit (grayscale) texture.
*
* @param source
* the source texture
* @return grayscale texture
*/
public static Image convertToGrayscaleTexture(Image source) {
BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
op.filter(sourceImage, sourceImage);
return ImageUtils.toJmeImage(sourceImage, source.getFormat());
}
/**
* This method decompresses the given image. If the given image is already
* decompressed nothing happens and it is simply returned.
*
* @param image
* the image to decompress
* @return the decompressed image
*/
public static Image decompress(Image image) {
Format format = image.getFormat();
int depth = image.getDepth();
if (depth == 0) {
depth = 1;
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1];
int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null;
for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
ByteBuffer data = image.getData(dataLayerIndex);
data.rewind();
if (sizes.length == 1) {
sizes[0] = data.remaining();
}
float widthToHeightRatio = image.getWidth() / image.getHeight();// this should always be constant for each mipmap
List<DDSTexelData> texelDataList = new ArrayList<DDSTexelData>(sizes.length);
int maxPosition = 0, resultSize = 0;
for (int sizeIndex = 0; sizeIndex < sizes.length; ++sizeIndex) {
maxPosition += sizes[sizeIndex];
DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format);
texelDataList.add(texelData);
switch (format) {
case DXT1:// BC1
case DXT1A:
while (data.position() < maxPosition) {
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
short c0 = data.getShort();
short c1 = data.getShort();
int col0 = RGB565.RGB565_to_ARGB8(c0);
int col1 = RGB565.RGB565_to_ARGB8(c1);
colors[0].fromARGB8(col0);
colors[1].fromARGB8(col1);
if (col0 > col1) {
// creating color2 = 2/3color0 + 1/3color1
colors[2].fromPixel(colors[0]);
colors[2].mult(2);
colors[2].add(colors[1]);
colors[2].divide(3);
// creating color3 = 1/3color0 + 2/3color1;
colors[3].fromPixel(colors[1]);
colors[3].mult(2);
colors[3].add(colors[0]);
colors[3].divide(3);
} else {
// creating color2 = 1/2color0 + 1/2color1
colors[2].fromPixel(colors[0]);
colors[2].add(colors[1]);
colors[2].mult(0.5f);
colors[3].fromARGB8(0);
}
int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
texelData.add(colors, indexes);
}
break;
case DXT3:// BC2
while (data.position() < maxPosition) {
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
long alpha = data.getLong();
float[] alphas = new float[16];
long alphasIndex = 0;
for (int i = 0; i < 16; ++i) {
alphasIndex |= i << i * 4;
byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4);
alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f;
}
short c0 = data.getShort();
short c1 = data.getShort();
int col0 = RGB565.RGB565_to_ARGB8(c0);
int col1 = RGB565.RGB565_to_ARGB8(c1);
colors[0].fromARGB8(col0);
colors[1].fromARGB8(col1);
// creating color2 = 2/3color0 + 1/3color1
colors[2].fromPixel(colors[0]);
colors[2].mult(2);
colors[2].add(colors[1]);
colors[2].divide(3);
// creating color3 = 1/3color0 + 2/3color1;
colors[3].fromPixel(colors[1]);
colors[3].mult(2);
colors[3].add(colors[0]);
colors[3].divide(3);
int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
texelData.add(colors, indexes, alphas, alphasIndex);
}
break;
case DXT5:// BC3
float[] alphas = new float[8];
while (data.position() < maxPosition) {
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
alphas[0] = data.get() * 255.0f;
alphas[1] = data.get() * 255.0f;
long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40;
if (alphas[0] > alphas[1]) {// 6 interpolated alpha values.
alphas[2] = (6 * alphas[0] + alphas[1]) / 7;
alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7;
alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7;
alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7;
alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7;
alphas[7] = (alphas[0] + 6 * alphas[1]) / 7;
} else {
alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f;
alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f;
alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f;
alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f;
alphas[6] = 0;
alphas[7] = 1;
}
short c0 = data.getShort();
short c1 = data.getShort();
int col0 = RGB565.RGB565_to_ARGB8(c0);
int col1 = RGB565.RGB565_to_ARGB8(c1);
colors[0].fromARGB8(col0);
colors[1].fromARGB8(col1);
// creating color2 = 2/3color0 + 1/3color1
colors[2].fromPixel(colors[0]);
colors[2].mult(2);
colors[2].add(colors[1]);
colors[2].divide(3);
// creating color3 = 1/3color0 + 2/3color1;
colors[3].fromPixel(colors[1]);
colors[3].mult(2);
colors[3].add(colors[0]);
colors[3].divide(3);
int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
texelData.add(colors, indexes, alphas, alphaIndices);
}
break;
default:
throw new IllegalStateException("Unknown compressed format: " + format);
}
newMipmapSizes[sizeIndex] = texelData.getSizeInBytes();
resultSize += texelData.getSizeInBytes();
}
byte[] bytes = new byte[resultSize];
int offset = 0;
byte[] pixelBytes = new byte[4];
for (DDSTexelData texelData : texelDataList) {
for (int i = 0; i < texelData.getPixelWidth(); ++i) {
for (int j = 0; j < texelData.getPixelHeight(); ++j) {
if (texelData.getRGBA8(i, j, pixelBytes)) {
bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0];
bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1];
bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2];
bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3];
} else {
break;
}
}
}
offset += texelData.getSizeInBytes();
}
dataArray.add(BufferUtils.createByteBuffer(bytes));
}
Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0));
if (newMipmapSizes != null) {
result.setMipMapSizes(newMipmapSizes);
}
return result;
}
/**
* This method returns the height represented by the specified pixel in the
* given texture. The given texture should be a height-map.
*
* @param image
* the height-map texture
* @param x
* pixel's X coordinate
* @param y
* pixel's Y coordinate
* @return height reprezented by the given texture in the specified location
*/
private static int getHeight(BufferedImage image, int x, int y) {
if (x < 0) {
x = 0;
} else if (x >= image.getWidth()) {
x = image.getWidth() - 1;
}
if (y < 0) {
y = 0;
} else if (y >= image.getHeight()) {
y = image.getHeight() - 1;
}
return image.getRGB(x, y) & 0xff;
}
/**
* This method transforms given vector's coordinates into ARGB color (A is
* always = 255).
*
* @param x
* X factor of the vector
* @param y
* Y factor of the vector
* @param z
* Z factor of the vector
* @return color representation of the given vector
*/
private static int vectorToColor(float x, float y, float z) {
int r = Math.round(255 * (x + 1f) / 2f);
int g = Math.round(255 * (y + 1f) / 2f);
int b = Math.round(255 * (z + 1f) / 2f);
return (255 << 24) + (r << 16) + (g << 8) + b;
}
/**
* Converts java awt image to jme image.
* @param bufferedImage
* the java awt image
* @param format
* the result image format
* @return the jme image
*/
private static Image toJmeImage(BufferedImage bufferedImage, Format format) {
ByteBuffer byteBuffer = BufferUtils.createByteBuffer(bufferedImage.getWidth() * bufferedImage.getHeight() * 3);
ImageToAwt.convert(bufferedImage, format, byteBuffer);
return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer);
}
}

@ -31,21 +31,17 @@
*/
package com.jme3.scene.plugins.blender.textures;
import com.jme3.asset.AssetInfo;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import jme3tools.converters.ImageToAwt;
import jme3tools.converters.RGB565;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetManager;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.asset.BlenderKey;
@ -53,20 +49,23 @@ import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.asset.GeneratedTextureKey;
import com.jme3.asset.TextureKey;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory;
import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorFactory;
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapMode;
@ -205,280 +204,6 @@ public class TextureHelper extends AbstractBlenderHelper {
return result;
}
/**
* This method converts the given texture into normal-map texture.
*
* @param source
* the source texture
* @param strengthFactor
* the normal strength factor
* @return normal-map texture
*/
public Image convertToNormalMapTexture(Image source, float strengthFactor) {
BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
gscale.filter(sourceImage, heightMap);
Vector3f S = new Vector3f();
Vector3f T = new Vector3f();
Vector3f N = new Vector3f();
for (int x = 0; x < bumpMap.getWidth(); ++x) {
for (int y = 0; y < bumpMap.getHeight(); ++y) {
// generating bump pixel
S.x = 1;
S.y = 0;
S.z = strengthFactor * this.getHeight(heightMap, x + 1, y) - strengthFactor * this.getHeight(heightMap, x - 1, y);
T.x = 0;
T.y = 1;
T.z = strengthFactor * this.getHeight(heightMap, x, y + 1) - strengthFactor * this.getHeight(heightMap, x, y - 1);
float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1);
N.x = -S.z;
N.y = -T.z;
N.z = 1;
N.divideLocal(den);
// setting thge pixel in the result image
bumpMap.setRGB(x, y, this.vectorToColor(N.x, N.y, N.z));
}
}
ByteBuffer byteBuffer = BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 3);
ImageToAwt.convert(bumpMap, Format.RGB8, byteBuffer);
return new Image(Format.RGB8, source.getWidth(), source.getHeight(), byteBuffer);
}
/**
* This method decompresses the given image. If the given image is already
* decompressed nothing happens and it is simply returned.
*
* @param image
* the image to decompress
* @return the decompressed image
*/
public Image decompress(Image image) {
Format format = image.getFormat();
int depth = image.getDepth();
if (depth == 0) {
depth = 1;
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1];
int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null;
for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
ByteBuffer data = image.getData(dataLayerIndex);
data.rewind();
if (sizes.length == 1) {
sizes[0] = data.remaining();
}
float widthToHeightRatio = image.getWidth() / image.getHeight();// this should always be constant for each mipmap
List<DDSTexelData> texelDataList = new ArrayList<DDSTexelData>(sizes.length);
int maxPosition = 0, resultSize = 0;
for (int sizeIndex = 0; sizeIndex < sizes.length; ++sizeIndex) {
maxPosition += sizes[sizeIndex];
DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format);
texelDataList.add(texelData);
switch (format) {
case DXT1:// BC1
case DXT1A:
while (data.position() < maxPosition) {
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
short c0 = data.getShort();
short c1 = data.getShort();
int col0 = RGB565.RGB565_to_ARGB8(c0);
int col1 = RGB565.RGB565_to_ARGB8(c1);
colors[0].fromARGB8(col0);
colors[1].fromARGB8(col1);
if (col0 > col1) {
// creating color2 = 2/3color0 + 1/3color1
colors[2].fromPixel(colors[0]);
colors[2].mult(2);
colors[2].add(colors[1]);
colors[2].divide(3);
// creating color3 = 1/3color0 + 2/3color1;
colors[3].fromPixel(colors[1]);
colors[3].mult(2);
colors[3].add(colors[0]);
colors[3].divide(3);
} else {
// creating color2 = 1/2color0 + 1/2color1
colors[2].fromPixel(colors[0]);
colors[2].add(colors[1]);
colors[2].mult(0.5f);
colors[3].fromARGB8(0);
}
int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
texelData.add(colors, indexes);
}
break;
case DXT3:// BC2
while (data.position() < maxPosition) {
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
long alpha = data.getLong();
float[] alphas = new float[16];
long alphasIndex = 0;
for (int i = 0; i < 16; ++i) {
alphasIndex |= i << i * 4;
byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4);
alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f;
}
short c0 = data.getShort();
short c1 = data.getShort();
int col0 = RGB565.RGB565_to_ARGB8(c0);
int col1 = RGB565.RGB565_to_ARGB8(c1);
colors[0].fromARGB8(col0);
colors[1].fromARGB8(col1);
// creating color2 = 2/3color0 + 1/3color1
colors[2].fromPixel(colors[0]);
colors[2].mult(2);
colors[2].add(colors[1]);
colors[2].divide(3);
// creating color3 = 1/3color0 + 2/3color1;
colors[3].fromPixel(colors[1]);
colors[3].mult(2);
colors[3].add(colors[0]);
colors[3].divide(3);
int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
texelData.add(colors, indexes, alphas, alphasIndex);
}
break;
case DXT5:// BC3
float[] alphas = new float[8];
while (data.position() < maxPosition) {
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
alphas[0] = data.get() * 255.0f;
alphas[1] = data.get() * 255.0f;
long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40;
if (alphas[0] > alphas[1]) {// 6 interpolated alpha values.
alphas[2] = (6 * alphas[0] + alphas[1]) / 7;
alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7;
alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7;
alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7;
alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7;
alphas[7] = (alphas[0] + 6 * alphas[1]) / 7;
} else {
alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f;
alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f;
alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f;
alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f;
alphas[6] = 0;
alphas[7] = 1;
}
short c0 = data.getShort();
short c1 = data.getShort();
int col0 = RGB565.RGB565_to_ARGB8(c0);
int col1 = RGB565.RGB565_to_ARGB8(c1);
colors[0].fromARGB8(col0);
colors[1].fromARGB8(col1);
// creating color2 = 2/3color0 + 1/3color1
colors[2].fromPixel(colors[0]);
colors[2].mult(2);
colors[2].add(colors[1]);
colors[2].divide(3);
// creating color3 = 1/3color0 + 2/3color1;
colors[3].fromPixel(colors[1]);
colors[3].mult(2);
colors[3].add(colors[0]);
colors[3].divide(3);
int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
texelData.add(colors, indexes, alphas, alphaIndices);
}
break;
default:
throw new IllegalStateException("Unknown compressed format: " + format);
}
newMipmapSizes[sizeIndex] = texelData.getSizeInBytes();
resultSize += texelData.getSizeInBytes();
}
byte[] bytes = new byte[resultSize];
int offset = 0;
byte[] pixelBytes = new byte[4];
for (DDSTexelData texelData : texelDataList) {
for (int i = 0; i < texelData.getPixelWidth(); ++i) {
for (int j = 0; j < texelData.getPixelHeight(); ++j) {
if (texelData.getRGBA8(i, j, pixelBytes)) {
bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0];
bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1];
bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2];
bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3];
} else {
break;
}
}
}
offset += texelData.getSizeInBytes();
}
dataArray.add(BufferUtils.createByteBuffer(bytes));
}
Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0));
if (newMipmapSizes != null) {
result.setMipMapSizes(newMipmapSizes);
}
return result;
}
/**
* This method returns the height represented by the specified pixel in the
* given texture. The given texture should be a height-map.
*
* @param image
* the height-map texture
* @param x
* pixel's X coordinate
* @param y
* pixel's Y coordinate
* @return height reprezented by the given texture in the specified location
*/
protected int getHeight(BufferedImage image, int x, int y) {
if (x < 0) {
x = 0;
} else if (x >= image.getWidth()) {
x = image.getWidth() - 1;
}
if (y < 0) {
y = 0;
} else if (y >= image.getHeight()) {
y = image.getHeight() - 1;
}
return image.getRGB(x, y) & 0xff;
}
/**
* This method transforms given vector's coordinates into ARGB color (A is
* always = 255).
*
* @param x
* X factor of the vector
* @param y
* Y factor of the vector
* @param z
* Z factor of the vector
* @return color representation of the given vector
*/
protected int vectorToColor(float x, float y, float z) {
int r = Math.round(255 * (x + 1f) / 2f);
int g = Math.round(255 * (y + 1f) / 2f);
int b = Math.round(255 * (z + 1f) / 2f);
return (255 << 24) + (r << 16) + (g << 8) + b;
}
/**
* This class returns a texture read from the file or from packed blender
* data.
@ -798,8 +523,95 @@ public class TextureHelper extends AbstractBlenderHelper {
return result;
}
@SuppressWarnings("unchecked")
public Map<Number, CombinedTexture> readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException {
DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
int separatedTextures = skyTexture ? 0 : ((Number) structure.getFieldValue("septex")).intValue();
List<TextureData> texturesList = new ArrayList<TextureData>();
for (int i = 0; i < mtexsArray.getTotalSize(); ++i) {
Pointer p = mtexsArray.get(i);
if (p.isNotNull() && (separatedTextures & 1 << i) == 0) {
TextureData textureData = new TextureData();
textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0);
textureData.uvCoordinatesType = skyTexture ? UVCoordinatesType.TEXCO_ORCO.blenderValue : ((Number) textureData.mtex.getFieldValue("texco")).intValue();
textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue();
textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString();
if(textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) {
textureData.uvCoordinatesName = null;
}
Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex");
if (pTex.isNotNull()) {
Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0);
textureData.textureStructure = tex;
texturesList.add(textureData);
}
}
}
// loading the textures and merging them
Map<Number, List<TextureData>> textureDataMap = this.sortTextures(texturesList);
Map<Number, CombinedTexture> loadedTextures = new HashMap<Number, CombinedTexture>();
for (Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) {
if (entry.getValue().size() > 0) {
CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue(), !skyTexture);
for (TextureData textureData : entry.getValue()) {
int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue();
boolean negateTexture = (texflag & 0x04) != 0;
Texture texture = this.getTexture(textureData.textureStructure, textureData.mtex, blenderContext);
if (texture != null) {
int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue();
float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() };
float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue();
TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac);
combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType,
textureData.textureStructure, textureData.uvCoordinatesName, blenderContext);
}
}
if (combinedTexture.getTexturesCount() > 0) {
loadedTextures.put(entry.getKey(), combinedTexture);
}
}
}
return loadedTextures;
}
/**
* This method sorts the textures by their mapping type. In each group only
* textures of one type are put (either two- or three-dimensional).
*
* @return a map with sorted textures
*/
private Map<Number, List<TextureData>> sortTextures(List<TextureData> textures) {
int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB };
Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
for (TextureData data : textures) {
Number mapto = (Number) data.mtex.getFieldValue("mapto");
for (int i = 0; i < mappings.length; ++i) {
if ((mappings[i] & mapto.intValue()) != 0) {
List<TextureData> datas = result.get(mappings[i]);
if (datas == null) {
datas = new ArrayList<TextureData>();
result.put(mappings[i], datas);
}
datas.add(data);
}
}
}
return result;
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0;
}
public static class TextureData {
public Structure mtex;
public Structure textureStructure;
public int uvCoordinatesType;
public int projectionType;
/** The name of the user's UV coordinates that are used for this texture. */
public String uvCoordinatesName;
}
}

@ -276,6 +276,20 @@ public class TexturePixel implements Cloneable {
this.alpha = (this.alpha + pixel.alpha) * 0.5f;
}
/**
* Mixes two pixels.
*
* @param pixel
* the pixel we mix with
*/
public void mix(TexturePixel pixel) {
this.red = 0.5f * (this.red + pixel.red);
this.green = 0.5f * (this.green + pixel.green);
this.blue = 0.5f * (this.blue + pixel.blue);
this.alpha = 0.5f * (this.alpha + pixel.alpha);
this.intensity = 0.5f * (this.intensity + pixel.intensity);
}
/**
* This method negates the colors.
*/
@ -307,6 +321,19 @@ public class TexturePixel implements Cloneable {
this.intensity += pixel.intensity;
}
/**
* This method adds the calues of the given pixel to the current pixel.
*
* @param pixel
* the pixel we add
*/
public void add(ColorRGBA pixel) {
this.red += pixel.r;
this.green += pixel.g;
this.blue += pixel.b;
this.alpha += pixel.a;
}
/**
* This method multiplies the values of the given pixel by the given value.
*

@ -168,33 +168,6 @@ import com.jme3.util.BufferUtils;
}
}
/**
* This method merges the current texture with the given one. The given
* texture is not changed.
*
* @param triangulatedTexture
* the texture we merge current texture with
*/
public void merge(TriangulatedTexture triangulatedTexture) {
TexturePixel sourcePixel = new TexturePixel();
TexturePixel targetPixel = new TexturePixel();
for (TriangleTextureElement triangleTextureElement : this.faceTextures) {
Image sourceImage = triangulatedTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image;
Image targetImage = triangleTextureElement.image;
PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
for (int x = 0; x < sourceImage.getWidth(); ++x) {
for (int y = 0; y < sourceImage.getHeight(); ++y) {
sourceIO.read(sourceImage, 0, sourcePixel, x, y);
targetIO.read(targetImage, 0, targetPixel, x, y);
targetPixel.merge(sourcePixel);
targetIO.write(targetImage, 0, targetPixel, x, y);
}
}
}
}
/**
* This method returns the flat texture. It is calculated if required or if
* it was not created before. Images that are identical are discarded to

Loading…
Cancel
Save