Textures loading refactoring.

New features:
- support for loading both 2D and 3D textures (they are merged and flattened to Texture2D)
- support for colorband usage in flat textures
- support for using color factors for flat images

Bugfixes:
- blend texture should be now calculated properly at the ends of the object
- blend texture is now properly directed
- flat texture projection (flat, bude and tube) should now be properly directed

Other stuff:
- better code separation and improved readability

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9329 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Kae..pl 13 years ago
parent f669290a3a
commit 8de8bf2d3e
  1. 7
      engine/src/blender/Common/MatDefs/Texture3D/tex3D.frag
  2. 16
      engine/src/blender/Common/MatDefs/Texture3D/tex3D.j3md
  3. 11
      engine/src/blender/Common/MatDefs/Texture3D/tex3D.vert
  4. 127
      engine/src/blender/com/jme3/asset/BlenderKey.java
  5. 35
      engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java
  6. 28
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java
  7. 16
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
  8. 20
      engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java
  9. 422
      engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  10. 145
      engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java
  11. 122
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  12. 315
      engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java
  13. 367
      engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  14. 131
      engine/src/blender/com/jme3/scene/plugins/blender/textures/DDSTexelData.java
  15. 151
      engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java
  16. 416
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGenerator.java
  17. 131
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorClouds.java
  18. 106
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorNoise.java
  19. 125
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorStucci.java
  20. 171
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorVoronoi.java
  21. 389
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java
  22. 146
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java
  23. 721
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java
  24. 227
      engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java
  25. 131
      engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java
  26. 149
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java
  27. 30
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java
  28. 242
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java
  29. 39
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java
  30. 33
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java
  31. 64
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java
  32. 19
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java
  33. 129
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java
  34. 66
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java
  35. 109
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java
  36. 74
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java
  37. 39
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java
  38. 79
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java
  39. 61
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java
  40. 81
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java
  41. 85
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java
  42. 101
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java
  43. 141
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java
  44. 70
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java
  45. 0
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/noiseconstants.dat
  46. 75
      engine/src/blender/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java
  47. 174
      engine/src/blender/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java
  48. 33
      engine/src/blender/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java
  49. 50
      engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java
  50. 65
      engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java

@ -1,7 +0,0 @@
uniform sampler3D m_Texture;
varying vec3 texCoord;
void main(){
gl_FragColor= texture3D(m_Texture,texCoord);
}

@ -1,16 +0,0 @@
MaterialDef My MaterialDef {
MaterialParameters {
Texture3D Texture
}
Technique {
VertexShader GLSL100: Common/MatDefs/Texture3D/tex3D.vert
FragmentShader GLSL100: Common/MatDefs/Texture3D/tex3D.frag
WorldParameters {
WorldViewProjectionMatrix
}
}
}

@ -1,11 +0,0 @@
uniform mat4 g_WorldViewProjectionMatrix;
attribute vec3 inTexCoord;
attribute vec3 inPosition;
varying vec3 texCoord;
void main(){
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);
texCoord=inTexCoord;
}

@ -31,6 +31,13 @@
*/ */
package com.jme3.asset; package com.jme3.asset;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import org.lwjgl.opengl.GL11;
import com.jme3.bounding.BoundingVolume; import com.jme3.bounding.BoundingVolume;
import com.jme3.collision.Collidable; import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResults; import com.jme3.collision.CollisionResults;
@ -49,10 +56,6 @@ import com.jme3.scene.SceneGraphVisitor;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.ogre.AnimData; import com.jme3.scene.plugins.ogre.AnimData;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
/** /**
* Blender key. Contains path of the blender file and its loading properties. * Blender key. Contains path of the blender file and its loading properties.
@ -66,12 +69,6 @@ public class BlenderKey extends ModelKey {
* between the frames. * between the frames.
*/ */
protected int fps = DEFAULT_FPS; protected int fps = DEFAULT_FPS;
/** Width of generated textures (in pixels). */
protected int generatedTextureWidth = 60;
/** Height of generated textures (in pixels). */
protected int generatedTextureHeight = 60;
/** Depth of generated textures (in pixels). */
protected int generatedTextureDepth = 60;
/** /**
* This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
*/ */
@ -82,6 +79,8 @@ public class BlenderKey extends ModelKey {
protected String assetRootPath; protected String assetRootPath;
/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */ /** 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;
/** /**
* The name of world settings that the importer will use. If not set or specified name does not occur in the file * 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. * then the first world settings in the file will be used.
@ -101,6 +100,8 @@ public class BlenderKey extends ModelKey {
protected int layersToLoad = -1; protected int layersToLoad = -1;
/** A variable that toggles the object custom properties loading. */ /** 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;
/** /**
* Constructor used by serialization mechanisms. * Constructor used by serialization mechanisms.
@ -133,57 +134,6 @@ public class BlenderKey extends ModelKey {
this.fps = fps; this.fps = fps;
} }
/**
* This method sets the width of generated texture (in pixels). By default the value is 140 px.
* @param generatedTextureWidth
* the width of generated texture
*/
public void setGeneratedTextureWidth(int generatedTextureWidth) {
this.generatedTextureWidth = generatedTextureWidth;
}
/**
* This method returns the width of generated texture (in pixels). By default the value is 140 px.
* @return the width of generated texture
*/
public int getGeneratedTextureWidth() {
return generatedTextureWidth;
}
/**
* This method sets the height of generated texture (in pixels). By default the value is 20 px.
* @param generatedTextureHeight
* the height of generated texture
*/
public void setGeneratedTextureHeight(int generatedTextureHeight) {
this.generatedTextureHeight = generatedTextureHeight;
}
/**
* This method returns the height of generated texture (in pixels). By default the value is 20 px.
* @return the height of generated texture
*/
public int getGeneratedTextureHeight() {
return generatedTextureHeight;
}
/**
* This method sets the depth of generated texture (in pixels). By default the value is 20 px.
* @param generatedTextureDepth
* the depth of generated texture
*/
public void setGeneratedTextureDepth(int generatedTextureDepth) {
this.generatedTextureDepth = generatedTextureDepth;
}
/**
* This method returns the depth of generated texture (in pixels). By default the value is 20 px.
* @return the depth of generated texture
*/
public int getGeneratedTextureDepth() {
return generatedTextureDepth;
}
/** /**
* This method returns the face cull mode. * This method returns the face cull mode.
* @return the face cull mode * @return the face cull mode
@ -234,6 +184,24 @@ public class BlenderKey extends ModelKey {
return loadObjectProperties; return loadObjectProperties;
} }
/**
* @return maximum texture size (width/height)
*/
public int getMaxTextureSize() {
if(maxTextureSize <= 0) {
maxTextureSize = GL11.glGetInteger(GL11.GL_MAX_TEXTURE_SIZE);
}
return maxTextureSize;
}
/**
* This method sets the maximum texture size.
* @param maxTextureSize the maximum texture size
*/
public void setMaxTextureSize(int maxTextureSize) {
this.maxTextureSize = maxTextureSize;
}
/** /**
* This method sets the asset root path. * This method sets the asset root path.
* @param assetRootPath * @param assetRootPath
@ -329,6 +297,21 @@ public class BlenderKey extends ModelKey {
return fixUpAxis; return fixUpAxis;
} }
/**
* This method sets the generated textures resolution.
* @param generatedTexturePPU the generated textures resolution
*/
public void setGeneratedTexturePPU(int generatedTexturePPU) {
this.generatedTexturePPU = generatedTexturePPU;
}
/**
* @return the generated textures resolution
*/
public int getGeneratedTexturePPU() {
return generatedTexturePPU;
}
/** /**
* This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is * 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 * 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
@ -370,13 +353,11 @@ public class BlenderKey extends ModelKey {
super.write(e); super.write(e);
OutputCapsule oc = e.getCapsule(this); OutputCapsule oc = e.getCapsule(this);
oc.write(fps, "fps", DEFAULT_FPS); oc.write(fps, "fps", DEFAULT_FPS);
oc.write(generatedTextureWidth, "generated-texture-width", 20);
oc.write(generatedTextureHeight, "generated-texture-height", 20);
oc.write(generatedTextureDepth, "generated-texture-depth", 20);
oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL); oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL);
oc.write(loadUnlinkedAssets, "load-unlinked-assets", false); oc.write(loadUnlinkedAssets, "load-unlinked-assets", false);
oc.write(assetRootPath, "asset-root-path", null); oc.write(assetRootPath, "asset-root-path", null);
oc.write(fixUpAxis, "fix-up-axis", true); oc.write(fixUpAxis, "fix-up-axis", true);
oc.write(generatedTexturePPU, "generated-texture-ppu", 128);
oc.write(usedWorld, "used-world", null); oc.write(usedWorld, "used-world", null);
oc.write(defaultMaterial, "default-material", null); oc.write(defaultMaterial, "default-material", null);
oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off); oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off);
@ -388,13 +369,11 @@ public class BlenderKey extends ModelKey {
super.read(e); super.read(e);
InputCapsule ic = e.getCapsule(this); InputCapsule ic = e.getCapsule(this);
fps = ic.readInt("fps", DEFAULT_FPS); fps = ic.readInt("fps", DEFAULT_FPS);
generatedTextureWidth = ic.readInt("generated-texture-width", 20);
generatedTextureHeight = ic.readInt("generated-texture-height", 20);
generatedTextureDepth = ic.readInt("generated-texture-depth", 20);
featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL); featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL);
loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false); loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false);
assetRootPath = ic.readString("asset-root-path", null); assetRootPath = ic.readString("asset-root-path", null);
fixUpAxis = ic.readBoolean("fix-up-axis", true); fixUpAxis = ic.readBoolean("fix-up-axis", true);
generatedTexturePPU = ic.readInt("generated-texture-ppu", 128);
usedWorld = ic.readString("used-world", null); usedWorld = ic.readString("used-world", null);
defaultMaterial = (Material) ic.readSavable("default-material", null); defaultMaterial = (Material) ic.readSavable("default-material", null);
faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off); faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off);
@ -411,9 +390,7 @@ public class BlenderKey extends ModelKey {
result = prime * result + featuresToLoad; result = prime * result + featuresToLoad;
result = prime * result + (fixUpAxis ? 1231 : 1237); result = prime * result + (fixUpAxis ? 1231 : 1237);
result = prime * result + fps; result = prime * result + fps;
result = prime * result + generatedTextureDepth; result = prime * result + generatedTexturePPU;
result = prime * result + generatedTextureHeight;
result = prime * result + generatedTextureWidth;
result = prime * result + layersToLoad; result = prime * result + layersToLoad;
result = prime * result + (loadUnlinkedAssets ? 1231 : 1237); result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode()); result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode());
@ -458,13 +435,7 @@ public class BlenderKey extends ModelKey {
if (fps != other.fps) { if (fps != other.fps) {
return false; return false;
} }
if (generatedTextureDepth != other.generatedTextureDepth) { if (generatedTexturePPU != other.generatedTexturePPU) {
return false;
}
if (generatedTextureHeight != other.generatedTextureHeight) {
return false;
}
if (generatedTextureWidth != other.generatedTextureWidth) {
return false; return false;
} }
if (layersToLoad != other.layersToLoad) { if (layersToLoad != other.layersToLoad) {
@ -483,6 +454,8 @@ public class BlenderKey extends ModelKey {
return true; return true;
} }
/** /**
* This interface describes the features of the scene that are to be loaded. * This interface describes the features of the scene that are to be loaded.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)

@ -31,12 +31,15 @@
*/ */
package com.jme3.scene.plugins.blender; package com.jme3.scene.plugins.blender;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.asset.AssetLoader; import com.jme3.asset.AssetLoader;
import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.asset.BlenderKey.WorldData; import com.jme3.asset.BlenderKey.WorldData;
import com.jme3.light.AmbientLight; import com.jme3.light.AmbientLight;
import com.jme3.light.Light; import com.jme3.light.Light;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
@ -47,12 +50,8 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.lights.LightHelper; import com.jme3.scene.plugins.blender.lights.LightHelper;
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
import com.jme3.scene.plugins.blender.meshes.MeshHelper; import com.jme3.scene.plugins.blender.meshes.MeshHelper;
import com.jme3.scene.plugins.blender.objects.ObjectHelper; import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* This class converts blender file blocks into jMonkeyEngine data structures. * This class converts blender file blocks into jMonkeyEngine data structures.
@ -150,19 +149,19 @@ import java.util.logging.Logger;
return null; return null;
} }
/** // /**
* This method converts the given structure to a material. // * This method converts the given structure to a material.
* @param structure // * @param structure
* structure of a material // * structure of a material
* @return material's node // * @return material's node
*/ // */
public Material toMaterial(Structure structure) throws BlenderFileException { // public Material toMaterial(Structure structure) throws BlenderFileException {
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); // MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
if (materialHelper.shouldBeLoaded(structure, blenderContext)) { // if (materialHelper.shouldBeLoaded(structure, blenderContext)) {
return materialHelper.toMaterial(structure, blenderContext); // return materialHelper.toMaterial(structure, blenderContext);
} // }
return null; // return null;
} // }
/** /**
* This method returns the data read from the WORLD file block. The block contains data that can be stored as * This method returns the data read from the WORLD file block. The block contains data that can be stored as

@ -53,7 +53,6 @@ import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.scene.plugins.blender.file.DnaBlockData; import com.jme3.scene.plugins.blender.file.DnaBlockData;
import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.meshes.MeshContext; import com.jme3.scene.plugins.blender.meshes.MeshContext;
import com.jme3.scene.plugins.blender.modifiers.Modifier; import com.jme3.scene.plugins.blender.modifiers.Modifier;
import com.jme3.scene.plugins.ogre.AnimData; import com.jme3.scene.plugins.ogre.AnimData;
@ -117,8 +116,6 @@ public class BlenderContext {
protected Map<Long, MeshContext> meshContexts = new HashMap<Long, MeshContext>(); protected Map<Long, MeshContext> meshContexts = new HashMap<Long, MeshContext>();
/** A map of bone contexts. */ /** A map of bone contexts. */
protected Map<Long, BoneContext> boneContexts = new HashMap<Long, BoneContext>(); protected Map<Long, BoneContext> boneContexts = new HashMap<Long, BoneContext>();
/** A map of material contexts. */
protected Map<Material, MaterialContext> materialContexts = new HashMap<Material, MaterialContext>();
/** A map og helpers that perform loading. */ /** A map og helpers that perform loading. */
private Map<String, AbstractBlenderHelper> helpers = new HashMap<String, AbstractBlenderHelper>(); private Map<String, AbstractBlenderHelper> helpers = new HashMap<String, AbstractBlenderHelper>();
@ -589,31 +586,6 @@ public class BlenderContext {
return boneContexts.get(boneOMA); return boneContexts.get(boneOMA);
} }
/**
* This method sets the material context for the given material. If the
* context is already set it will be replaced.
*
* @param material
* the material
* @param materialContext
* the material's context
*/
public void setMaterialContext(Material material, MaterialContext materialContext) {
this.materialContexts.put(material, materialContext);
}
/**
* This method returns the material context for the given material. If no
* context exists then <b>null</b> is returned.
*
* @param material
* the material
* @return material's context
*/
public MaterialContext getMaterialContext(Material material) {
return materialContexts.get(material);
}
/** /**
* This metod returns the default material. * This metod returns the default material.
* *

@ -103,11 +103,11 @@ public class BlenderLoader extends AbstractBlenderLoader {
} }
} }
break; break;
case FileBlockHeader.BLOCK_MA00:// Material // case FileBlockHeader.BLOCK_MA00:// Material
if (blenderKey.isLoadUnlinkedAssets() && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { // if (blenderKey.isLoadUnlinkedAssets() && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext))); // loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext)));
} // }
break; // break;
case FileBlockHeader.BLOCK_SC00:// Scene case FileBlockHeader.BLOCK_SC00:// Scene
if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) { if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) {
loadingResults.addScene(this.toScene(block.getStructure(blenderContext))); loadingResults.addScene(this.toScene(block.getStructure(blenderContext)));
@ -203,11 +203,7 @@ public class BlenderLoader extends AbstractBlenderLoader {
blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
// setting additional data to helpers // reading the blocks (dna block is automatically saved in the blender context when found)
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
materialHelper.setFaceCullMode(blenderKey.getFaceCullMode());
// reading the blocks (dna block is automatically saved in the blender context when found)//TODO: zmienić to
FileBlockHeader sceneFileBlock = null; FileBlockHeader sceneFileBlock = null;
do { do {
fileBlock = new FileBlockHeader(inputStream, blenderContext); fileBlock = new FileBlockHeader(inputStream, blenderContext);

@ -11,6 +11,7 @@ import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.*; import com.jme3.scene.plugins.blender.file.*;
import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.scene.plugins.blender.materials.MaterialHelper;
import com.jme3.scene.plugins.blender.meshes.MeshHelper; import com.jme3.scene.plugins.blender.meshes.MeshHelper;
import com.jme3.scene.plugins.blender.objects.Properties; import com.jme3.scene.plugins.blender.objects.Properties;
@ -88,12 +89,15 @@ public class CurvesHelper extends AbstractBlenderHelper {
//getting materials //getting materials
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
Material[] materials = materialHelper.getMaterials(curveStructure, blenderContext); MaterialContext[] materialContexts = materialHelper.getMaterials(curveStructure, blenderContext);
if (materials == null) { Material defaultMaterial = null;
materials = new Material[]{blenderContext.getDefaultMaterial().clone()}; if (materialContexts != null) {
for (MaterialContext materialContext : materialContexts) {
materialContext.setFaceCullMode(FaceCullMode.Off);
} }
for (Material material : materials) { } else {
material.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); defaultMaterial = blenderContext.getDefaultMaterial().clone();
defaultMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
} }
//getting or creating bevel object //getting or creating bevel object
@ -179,7 +183,11 @@ public class CurvesHelper extends AbstractBlenderHelper {
} }
if (nurbGeoms != null) {//setting the name and assigning materials if (nurbGeoms != null) {//setting the name and assigning materials
for (Geometry nurbGeom : nurbGeoms) { for (Geometry nurbGeom : nurbGeoms) {
nurbGeom.setMaterial(materials[nurbEntry.getKey().intValue()]); if(materialContexts != null) {
materialContexts[nurbEntry.getKey().intValue()].applyMaterial(nurbGeom, curveStructure.getOldMemoryAddress(), false, null, blenderContext);
} else {
nurbGeom.setMaterial(defaultMaterial);
}
nurbGeom.setName(name); nurbGeom.setName(name);
result.add(nurbGeom); result.add(nurbGeom);
} }

@ -1,6 +1,22 @@
package com.jme3.scene.plugins.blender.materials; package com.jme3.scene.plugins.blender.materials;
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.Logger;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.VertexBuffer;
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.BlenderContext;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.DynamicArray;
@ -8,19 +24,12 @@ import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure; 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.DiffuseShader;
import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader; 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.TextureHelper;
import com.jme3.scene.plugins.blender.textures.blending.TextureBlender; 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.blending.TextureBlenderFactory;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.Texture.Type; import com.jme3.util.BufferUtils;
import com.jme3.texture.Texture.WrapMode;
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;
/** /**
* This class holds the data about the material. * This class holds the data about the material.
@ -37,12 +46,7 @@ public final class MaterialContext {
public static final int MTEX_ALPHA = 0x80; public static final int MTEX_ALPHA = 0x80;
/* package */final String name; /* package */final String name;
/* package */final List<Structure> mTexs; /* package */final Map<Number, CombinedTexture> loadedTextures;
/* package */final List<Structure> textures;
/* package */final Map<Number, Texture> loadedTextures;
/* package */final Map<Texture, Structure> textureToMTexMap;
/* package */final int texturesCount;
/* package */final Type textureType;
/* package */final ColorRGBA diffuseColor; /* package */final ColorRGBA diffuseColor;
/* package */final DiffuseShader diffuseShader; /* package */final DiffuseShader diffuseShader;
@ -54,9 +58,7 @@ public final class MaterialContext {
/* package */final boolean vertexColor; /* package */final boolean vertexColor;
/* package */final boolean transparent; /* package */final boolean transparent;
/* package */final boolean vTangent; /* package */final boolean vTangent;
/* package */FaceCullMode faceCullMode;
/* package */int uvCoordinatesType = -1;
/* package */int projectionType;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
/* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { /* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
@ -97,85 +99,61 @@ public final class MaterialContext {
this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS; this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS;
} }
mTexs = new ArrayList<Structure>();
textures = new ArrayList<Structure>();
DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex"); DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue(); int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue();
Type firstTextureType = null; List<TextureData> texturesList = new ArrayList<TextureData>();
for (int i = 0; i < mtexsArray.getTotalSize(); ++i) { for (int i = 0; i < mtexsArray.getTotalSize(); ++i) {
Pointer p = mtexsArray.get(i); Pointer p = mtexsArray.get(i);
if (p.isNotNull() && (separatedTextures & 1 << i) == 0) { if (p.isNotNull() && (separatedTextures & 1 << i) == 0) {
Structure mtex = p.fetchData(blenderContext.getInputStream()).get(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();
// the first texture determines the texture coordinates type Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex");
if (uvCoordinatesType == -1) {
uvCoordinatesType = ((Number) mtex.getFieldValue("texco")).intValue();
projectionType = ((Number) mtex.getFieldValue("mapping")).intValue();
} else if (uvCoordinatesType != ((Number) mtex.getFieldValue("texco")).intValue()) {
LOGGER.log(Level.WARNING, "The texture with index: {0} has different UV coordinates type than the first texture! This texture will NOT be loaded!", i + 1);
continue;
}
Pointer pTex = (Pointer) mtex.getFieldValue("tex");
if (pTex.isNotNull()) { if (pTex.isNotNull()) {
Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0); Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0);
int type = ((Number) tex.getFieldValue("type")).intValue(); textureData.textureStructure = tex;
Type textureType = this.getType(type); texturesList.add(textureData);
if (textureType != null) {
if (firstTextureType == null) {
firstTextureType = textureType;
mTexs.add(mtex);
textures.add(tex);
} else if (firstTextureType == textureType) {
mTexs.add(mtex);
textures.add(tex);
} else {
LOGGER.log(Level.WARNING, "The texture with index: {0} is of different dimension than the first one! This texture will NOT be loaded!", i + 1);
}
}
} }
} }
} }
//loading the textures and merging them //loading the textures and merging them
Map<Number, List<Structure[]>> sortedTextures = this.sortAndFilterTextures(); Map<Number, List<TextureData>> textureDataMap = this.sortAndFilterTextures(texturesList);
loadedTextures = new HashMap<Number, Texture>(sortedTextures.size()); loadedTextures = new HashMap<Number, CombinedTexture>();
textureToMTexMap = new HashMap<Texture, Structure>();
float[] diffuseColorArray = new float[] {diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a}; float[] diffuseColorArray = new float[] {diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a};
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
for(Entry<Number, List<Structure[]>> entry : sortedTextures.entrySet()) { for(Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) {
if(entry.getValue().size()>0) { if(entry.getValue().size()>0) {
List<Texture> textures = new ArrayList<Texture>(entry.getValue().size()); CombinedTexture combinedTexture = loadedTextures.get(entry.getKey());
for(Structure[] mtexAndTex : entry.getValue()) { if(combinedTexture == null) {
int texflag = ((Number) mtexAndTex[0].getFieldValue("texflag")).intValue(); combinedTexture = new CombinedTexture();
loadedTextures.put(entry.getKey(), combinedTexture);
}
for(TextureData textureData : entry.getValue()) {
int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue();
boolean negateTexture = (texflag & 0x04) != 0; boolean negateTexture = (texflag & 0x04) != 0;
Texture texture = textureHelper.getTexture(mtexAndTex[1], blenderContext); Texture texture = textureHelper.getTexture(textureData.textureStructure, textureData.mtex, blenderContext);
int blendType = ((Number) mtexAndTex[0].getFieldValue("blendtype")).intValue(); int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue();
float[] color = new float[] { ((Number) mtexAndTex[0].getFieldValue("r")).floatValue(), float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(),
((Number) mtexAndTex[0].getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(),
((Number) mtexAndTex[0].getFieldValue("b")).floatValue() }; ((Number) textureData.mtex.getFieldValue("b")).floatValue() };
float colfac = ((Number) mtexAndTex[0].getFieldValue("colfac")).floatValue(); float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue();
TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat()); TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(),
texture = textureBlender.blend(diffuseColorArray, texture, color, colfac, blendType, negateTexture, blenderContext); texflag, negateTexture, blendType, diffuseColorArray, color, colfac);
texture.setWrap(WrapMode.Repeat); combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, blenderContext);
textures.add(texture);
textureToMTexMap.put(texture, mtexAndTex[0]);
} }
loadedTextures.put(entry.getKey(), textureHelper.mergeTextures(textures, this));
} }
} }
this.texturesCount = mTexs.size();
this.textureType = firstTextureType;
//veryfying if the transparency is present //veryfying if the transparency is present
//(in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when //(in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when
//it is not required //it is not required
boolean transparent = false; boolean transparent = false;
if(diffuseColor != null) { if(diffuseColor != null) {
transparent = diffuseColor.a < 1.0f; transparent = diffuseColor.a < 1.0f;
if(sortedTextures.size() > 0) {//texutre covers the material color if(textureDataMap.size() > 0) {//texutre covers the material color
diffuseColor.set(1, 1, 1, 1); diffuseColor.set(1, 1, 1, 1);
} }
} }
@ -188,107 +166,128 @@ public final class MaterialContext {
this.transparent = transparent; this.transparent = transparent;
} }
/** public void applyMaterial(Geometry geometry, Long geometriesOMA, boolean noTextures, List<Vector2f> userDefinedUVCoordinates, BlenderContext blenderContext) {
* This method sorts the textures by their mapping type. Material material = null;
* In each group only textures of one type are put (either two- or three-dimensional). if (shadeless) {
* If the mapping type is MTEX_COL then if the texture has no alpha channel then all textures before it are material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
* 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 if (!transparent) {
*/ diffuseColor.a = 1;
private Map<Number, List<Structure[]>> sortAndFilterTextures() {
Map<Number, List<Structure[]>> result = new HashMap<Number, List<Structure[]>>();
for (int i = 0; i < mTexs.size(); ++i) {
Structure mTex = mTexs.get(i);
Structure texture = textures.get(i);
Number mapto = (Number) mTex.getFieldValue("mapto");
List<Structure[]> mtexs = result.get(mapto);
if(mtexs==null) {
mtexs = new ArrayList<Structure[]>();
result.put(mapto, mtexs);
}
if(mapto.intValue() == MTEX_COL && this.isWithoutAlpha(textures.get(i))) {
mtexs.clear();//remove previous textures, they will be covered anyway
}
mtexs.add(new Structure[] {mTex, texture});
}
return result;
} }
/** material.setColor("Color", diffuseColor);
* This method determines if the given texture has no alpha channel. } else {
* material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
* @param texture material.setBoolean("UseMaterialColors", Boolean.TRUE);
* the texture to check for alpha channel
* @return <b>true</b> if the texture has no alpha channel and <b>false</b> // setting the colors
* otherwise material.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT);
*/ if (!transparent) {
private boolean isWithoutAlpha(Structure texture) { diffuseColor.a = 1;
int flag = ((Number) texture.getFieldValue("flag")).intValue();
if((flag & 0x01) == 0) {//the texture has no colorband
int type = ((Number) texture.getFieldValue("type")).intValue();
if(type==TextureHelper.TEX_MAGIC) {
return true;
} }
if(type==TextureHelper.TEX_VORONOI) { material.setColor("Diffuse", diffuseColor);
int voronoiColorType = ((Number) texture.getFieldValue("vn_coltype")).intValue();
return voronoiColorType != 0;//voronoiColorType == 0: intensity, voronoiColorType != 0: col1, col2 or col3 material.setBoolean("WardIso", specularShader == SpecularShader.WARDISO);
material.setColor("Specular", specularColor);
material.setColor("Ambient", ambientColor);
material.setFloat("Shininess", shininess);
} }
if(type==TextureHelper.TEX_CLOUDS) {
int sType = ((Number) texture.getFieldValue("stype")).intValue(); //applying textures
return sType == 1;//sType==0: without colors, sType==1: with colors for(Entry<Number, CombinedTexture> entry : loadedTextures.entrySet()) {
CombinedTexture combinedTexture = entry.getValue();
combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
VertexBuffer.Type uvCoordinatesType = null;
switch(entry.getKey().intValue()) {
case MTEX_COL:
uvCoordinatesType = VertexBuffer.Type.TexCoord;
material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE,
combinedTexture.getResultTexture());
break;
case MTEX_NOR:
uvCoordinatesType = VertexBuffer.Type.TexCoord2;
material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, combinedTexture.getResultTexture());
break;
case MTEX_SPEC:
uvCoordinatesType = VertexBuffer.Type.TexCoord3;
material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, combinedTexture.getResultTexture());
break;
case MTEX_EMIT:
uvCoordinatesType = VertexBuffer.Type.TexCoord4;
material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, combinedTexture.getResultTexture());
break;
case MTEX_ALPHA:
uvCoordinatesType = VertexBuffer.Type.TexCoord5;
material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, combinedTexture.getResultTexture());
break;
default:
LOGGER.severe("Unknown mapping type: " + entry.getKey().intValue());
} }
//applying texture coordinates
if(uvCoordinatesType != null) {
VertexBuffer uvCoordsBuffer = new VertexBuffer(uvCoordinatesType);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float,
BufferUtils.createFloatBuffer(combinedTexture.getResultUVS().toArray(new Vector2f[combinedTexture.getResultUVS().size()])));
geometry.getMesh().setBuffer(uvCoordsBuffer);
} }
return false;
} }
/** //applying additional data
* This method returns the current material's texture UV coordinates type. material.setName(name);
* @return uv coordinates type if (vertexColor) {
*/ material.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true);
public int getUvCoordinatesType() { }
return uvCoordinatesType; if(this.faceCullMode != null) {
material.getAdditionalRenderState().setFaceCullMode(faceCullMode);
} else {
material.getAdditionalRenderState().setFaceCullMode(blenderContext.getBlenderKey().getFaceCullMode());
}
if (transparent) {
material.setTransparent(true);
material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
geometry.setQueueBucket(Bucket.Transparent);
} }
/** geometry.setMaterial(material);
* This method returns the proper projection type for the material's texture.
* This applies only to 2D textures.
* @return texture's projection type
*/
public int getProjectionType() {
return projectionType;
} }
/** /**
* This method returns current material's texture dimension. * This method sorts the textures by their mapping type.
* @return the material's texture dimension * 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
*/ */
public int getTextureDimension() { private Map<Number, List<TextureData>> sortAndFilterTextures(List<TextureData> textures) {
return this.textureType == Type.TwoDimensional ? 2 : 3; Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
for (TextureData data : textures) {
Number mapto = (Number) data.mtex.getFieldValue("mapto");
List<TextureData> datas = result.get(mapto);
if(datas==null) {
datas = new ArrayList<TextureData>();
result.put(mapto, datas);
}
datas.add(data);
}
return result;
} }
/** /**
* This method returns the amount of textures applied for the current * This method sets the face cull mode.
* material. * @param faceCullMode the face cull mode
*
* @return the amount of textures applied for the current material
*/ */
public int getTexturesCount() { public void setFaceCullMode(FaceCullMode faceCullMode) {
return textures == null ? 0 : textures.size(); this.faceCullMode = faceCullMode;
} }
/** /**
* This method returns the projection array that indicates where the current coordinate factor X, Y or Z (represented * @return the face cull mode
* by the index in the array) will be used where (indicated by the value in the array).
* For example the configuration: [1,2,3] means that X - coordinate will be used as X, Y as Y and Z as Z.
* The configuration [2,1,0] means that Z will be used instead of X coordinate, Y will be used as Y and
* Z will not be used at all (0 will be in its place).
* @param textureIndex
* the index of the texture
* @return texture projection array
*/ */
public int[] getProjection(int textureIndex) { public FaceCullMode getFaceCullMode() {
Structure mtex = mTexs.get(textureIndex); return faceCullMode;
return new int[] { ((Number) mtex.getFieldValue("projx")).intValue(), ((Number) mtex.getFieldValue("projy")).intValue(), ((Number) mtex.getFieldValue("projz")).intValue() };
} }
/** /**
@ -359,125 +358,10 @@ public final class MaterialContext {
return new ColorRGBA(r, g, b, alpha); return new ColorRGBA(r, g, b, alpha);
} }
/** private static class TextureData {
* This method determines the type of the texture. Structure mtex;
* @param texType Structure textureStructure;
* texture type (from blender) int uvCoordinatesType;
* @return texture type (used by jme) int projectionType;
*/
private Type getType(int texType) {
switch (texType) {
case TextureHelper.TEX_IMAGE:// (it is first because probably this will be most commonly used)
return Type.TwoDimensional;
case TextureHelper.TEX_CLOUDS:
case TextureHelper.TEX_WOOD:
case TextureHelper.TEX_MARBLE:
case TextureHelper.TEX_MAGIC:
case TextureHelper.TEX_BLEND:
case TextureHelper.TEX_STUCCI:
case TextureHelper.TEX_NOISE:
case TextureHelper.TEX_MUSGRAVE:
case TextureHelper.TEX_VORONOI:
case TextureHelper.TEX_DISTNOISE:
return Type.ThreeDimensional;
case TextureHelper.TEX_NONE:// No texture, do nothing
return null;
case TextureHelper.TEX_POINTDENSITY:
case TextureHelper.TEX_VOXELDATA:
case TextureHelper.TEX_PLUGIN:
case TextureHelper.TEX_ENVMAP:
LOGGER.log(Level.WARNING, "Texture type NOT supported: {0}", texType);
return null;
default:
throw new IllegalStateException("Unknown texture type: " + texType);
}
}
/**
* @return he material's name
*/
public String getName() {
return name;
}
/**
* @return a copy of diffuse color
*/
public ColorRGBA getDiffuseColor() {
return diffuseColor.clone();
}
/**
* @return an enum describing the type of a diffuse shader used by this material
*/
public DiffuseShader getDiffuseShader() {
return diffuseShader;
}
/**
* @return a copy of specular color
*/
public ColorRGBA getSpecularColor() {
return specularColor.clone();
}
/**
* @return an enum describing the type of a specular shader used by this material
*/
public SpecularShader getSpecularShader() {
return specularShader;
}
/**
* @return an ambient color used by the material
*/
public ColorRGBA getAmbientColor() {
return ambientColor;
}
/**
* @return the sihiness of this material
*/
public float getShininess() {
return shininess;
}
/**
* @return <b>true</b> if the material is shadeless and <b>false</b> otherwise
*/
public boolean isShadeless() {
return shadeless;
}
/**
* @return <b>true</b> if the material uses vertex color and <b>false</b> otherwise
*/
public boolean isVertexColor() {
return vertexColor;
}
/**
* @return <b>true</b> if the material is transparent and <b>false</b> otherwise
*/
public boolean isTransparent() {
return transparent;
}
/**
* @return <b>true</b> if the material uses tangents and <b>false</b> otherwise
*/
public boolean isvTangent() {
return vTangent;
}
/**
* @param texture
* the texture for which its mtex structure definition will be
* fetched
* @return mtex structure of the current texture or <b>null</b> if none
* exists
*/
public Structure getMTex(Texture texture) {
return textureToMTexMap.get(texture);
} }
} }

@ -31,12 +31,18 @@
*/ */
package com.jme3.scene.plugins.blender.materials; package com.jme3.scene.plugins.blender.materials;
import java.nio.ByteBuffer;
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 com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.material.MatParam; import com.jme3.material.MatParam;
import com.jme3.material.MatParamTexture; import com.jme3.material.MatParamTexture;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
@ -49,21 +55,12 @@ import com.jme3.shader.VarType;
import com.jme3.texture.Image; import com.jme3.texture.Image;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.Texture.Type;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
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;
public class MaterialHelper extends AbstractBlenderHelper { public class MaterialHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName());
protected static final float DEFAULT_SHININESS = 20.0f; protected static final float DEFAULT_SHININESS = 20.0f;
public static final String TEXTURE_TYPE_3D = "Texture";
public static final String TEXTURE_TYPE_COLOR = "ColorMap"; public static final String TEXTURE_TYPE_COLOR = "ColorMap";
public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap"; public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap";
public static final String TEXTURE_TYPE_NORMAL = "NormalMap"; public static final String TEXTURE_TYPE_NORMAL = "NormalMap";
@ -91,9 +88,6 @@ public class MaterialHelper extends AbstractBlenderHelper {
COOKTORRENCE, PHONG, BLINN, TOON, WARDISO COOKTORRENCE, PHONG, BLINN, TOON, WARDISO
} }
/** Face cull mode. Should be excplicitly set before this helper is used. */
protected FaceCullMode faceCullMode;
/** /**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
* versions. * versions.
@ -165,16 +159,6 @@ public class MaterialHelper extends AbstractBlenderHelper {
}); });
} }
/**
* This method sets the face cull mode to be used with every loaded material.
*
* @param faceCullMode
* the face cull mode
*/
public void setFaceCullMode(FaceCullMode faceCullMode) {
this.faceCullMode = faceCullMode;
}
/** /**
* This method converts the material structure to jme Material. * This method converts the material structure to jme Material.
* @param structure * @param structure
@ -185,105 +169,15 @@ public class MaterialHelper extends AbstractBlenderHelper {
* @throws BlenderFileException * @throws BlenderFileException
* an exception is throw when problems with blend file occur * an exception is throw when problems with blend file occur
*/ */
public Material toMaterial(Structure structure, BlenderContext blenderContext) throws BlenderFileException { public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.INFO, "Loading material."); LOGGER.log(Level.INFO, "Loading material.");
if (structure == null) { MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
return blenderContext.getDefaultMaterial();
}
Material result = (Material) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (result != null) { if (result != null) {
return result; return result;
} }
MaterialContext materialContext = new MaterialContext(structure, blenderContext); result = new MaterialContext(structure, blenderContext);
LOGGER.log(Level.INFO, "Material's name: {0}", materialContext.name); LOGGER.log(Level.INFO, "Material's name: {0}", result.name);
if(materialContext.textures.size() > 1) {
LOGGER.log(Level.WARNING, "Attetion! Many textures found for material: {0}. Only the first of each supported mapping types will be used!", materialContext.name);
}
// texture
Type colorTextureType = null;
Map<String, Texture> texturesMap = new HashMap<String, Texture>();
for(Entry<Number, Texture> textureEntry : materialContext.loadedTextures.entrySet()) {
int mapto = textureEntry.getKey().intValue();
Texture texture = textureEntry.getValue();
if ((mapto & MaterialContext.MTEX_COL) != 0) {
colorTextureType = texture.getType();
if (materialContext.shadeless) {
texturesMap.put(colorTextureType==Type.ThreeDimensional ? TEXTURE_TYPE_3D : TEXTURE_TYPE_COLOR, texture);
} else {
texturesMap.put(colorTextureType==Type.ThreeDimensional ? TEXTURE_TYPE_3D : TEXTURE_TYPE_DIFFUSE, texture);
}
}
if(texture.getType()==Type.TwoDimensional) {//so far only 2D textures can be mapped in other way than color
if ((mapto & MaterialContext.MTEX_NOR) != 0 && !materialContext.shadeless) {
//Structure mTex = materialContext.getMTex(texture);
//Texture normalMapTexture = textureHelper.convertToNormalMapTexture(texture, ((Number) mTex.getFieldValue("norfac")).floatValue());
//texturesMap.put(TEXTURE_TYPE_NORMAL, normalMapTexture);
texturesMap.put(TEXTURE_TYPE_NORMAL, texture);
}
if ((mapto & MaterialContext.MTEX_EMIT) != 0) {
texturesMap.put(TEXTURE_TYPE_GLOW, texture);
}
if ((mapto & MaterialContext.MTEX_SPEC) != 0 && !materialContext.shadeless) {
texturesMap.put(TEXTURE_TYPE_SPECULAR, texture);
}
if ((mapto & MaterialContext.MTEX_ALPHA) != 0 && !materialContext.shadeless) {
texturesMap.put(TEXTURE_TYPE_ALPHA, texture);
}
}
}
//creating the material
if(colorTextureType==Type.ThreeDimensional) {
result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Texture3D/tex3D.j3md");
} else {
if (materialContext.shadeless) {
result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
if (!materialContext.transparent) {
materialContext.diffuseColor.a = 1;
}
result.setColor("Color", materialContext.diffuseColor);
} else {
result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
result.setBoolean("UseMaterialColors", Boolean.TRUE);
// setting the colors
result.setBoolean("Minnaert", materialContext.diffuseShader == DiffuseShader.MINNAERT);
if (!materialContext.transparent) {
materialContext.diffuseColor.a = 1;
}
result.setColor("Diffuse", materialContext.diffuseColor);
result.setBoolean("WardIso", materialContext.specularShader == SpecularShader.WARDISO);
result.setColor("Specular", materialContext.specularColor);
result.setColor("Ambient", materialContext.ambientColor);
result.setFloat("Shininess", materialContext.shininess);
}
if (materialContext.vertexColor) {
result.setBoolean(materialContext.shadeless ? "VertexColor" : "UseVertexColor", true);
}
}
//applying textures
for(Entry<String, Texture> textureEntry : texturesMap.entrySet()) {
result.setTexture(textureEntry.getKey(), textureEntry.getValue());
}
//applying other data
result.getAdditionalRenderState().setFaceCullMode(faceCullMode);
if (materialContext.transparent) {
result.setTransparent(true);
result.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
}
result.setName(materialContext.getName());
blenderContext.setMaterialContext(result, materialContext);
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result); blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result);
return result; return result;
} }
@ -392,9 +286,6 @@ public class MaterialHelper extends AbstractBlenderHelper {
*/ */
public boolean hasTexture(Material material) { public boolean hasTexture(Material material) {
if (material != null) { if (material != null) {
if (material.getTextureParam(TEXTURE_TYPE_3D) != null) {
return true;
}
if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != null) { if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != null) {
return true; return true;
} }
@ -445,21 +336,17 @@ public class MaterialHelper extends AbstractBlenderHelper {
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted * this exception is thrown when the blend file structure is somehow invalid or corrupted
*/ */
public Material[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException { public MaterialContext[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException {
Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat"); Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat");
Material[] materials = null; MaterialContext[] materials = null;
if (ppMaterials.isNotNull()) { if (ppMaterials.isNotNull()) {
List<Structure> materialStructures = ppMaterials.fetchData(blenderContext.getInputStream()); List<Structure> materialStructures = ppMaterials.fetchData(blenderContext.getInputStream());
if (materialStructures != null && materialStructures.size() > 0) { if (materialStructures != null && materialStructures.size() > 0) {
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
materials = new Material[materialStructures.size()]; materials = new MaterialContext[materialStructures.size()];
int i = 0; int i = 0;
for (Structure s : materialStructures) { for (Structure s : materialStructures) {
Material material = (Material) blenderContext.getLoadedFeature(s.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); materials[i++] = materialHelper.toMaterialContext(s, blenderContext);
if (material == null) {
material = materialHelper.toMaterial(s, blenderContext);
}
materials[i++] = material;
} }
} }
} }

@ -40,11 +40,9 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.material.Material;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer;
@ -62,7 +60,6 @@ import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.scene.plugins.blender.materials.MaterialHelper;
import com.jme3.scene.plugins.blender.objects.Properties; import com.jme3.scene.plugins.blender.objects.Properties;
import com.jme3.scene.plugins.blender.textures.TextureHelper; import com.jme3.scene.plugins.blender.textures.TextureHelper;
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
@ -135,7 +132,7 @@ public class MeshHelper extends AbstractBlenderHelper {
} }
Pointer pMTFace = (Pointer) structure.getFieldValue("mtface"); Pointer pMTFace = (Pointer) structure.getFieldValue("mtface");
List<Vector2f> uvCoordinates = null; Map<Integer, List<Vector2f>> uvCoordinates = new HashMap<Integer, List<Vector2f>>();//<material_number; list of uv coordinates for mesh's vertices>
List<Structure> mtFaces = null; List<Structure> mtFaces = null;
if (pMTFace.isNotNull()) { if (pMTFace.isNotNull()) {
@ -144,7 +141,6 @@ public class MeshHelper extends AbstractBlenderHelper {
if (mtFaces.size() != facesAmount) { if (mtFaces.size() != facesAmount) {
throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!"); throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!");
} }
uvCoordinates = new ArrayList<Vector2f>();
} }
// normalMap merges normals of faces that will be rendered smooth // normalMap merges normals of faces that will be rendered smooth
@ -160,21 +156,28 @@ public class MeshHelper extends AbstractBlenderHelper {
int vertexColorIndex = 0; int vertexColorIndex = 0;
for (int i = 0; i < mFaces.size(); ++i) { for (int i = 0; i < mFaces.size(); ++i) {
Structure mFace = mFaces.get(i); Structure mFace = mFaces.get(i);
int matNr = ((Number) mFace.getFieldValue("mat_nr")).intValue();
boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
DynamicArray<Number> uvs = null; DynamicArray<Number> uvs = null;
boolean materialWithoutTextures = false; boolean materialWithoutTextures = false;
Pointer pImage = null; Pointer pImage = null;
List<Vector2f> uvCoordinatesList = uvCoordinates.get(Integer.valueOf(matNr));
if(uvCoordinatesList == null) {
uvCoordinatesList = new ArrayList<Vector2f>();
uvCoordinates.put(Integer.valueOf(matNr), uvCoordinatesList);
}
if (mtFaces != null) { if (mtFaces != null) {
Structure mtFace = mtFaces.get(i); Structure mtFace = mtFaces.get(i);
pImage = (Pointer) mtFace.getFieldValue("tpage"); pImage = (Pointer) mtFace.getFieldValue("tpage");
materialWithoutTextures = pImage.isNull(); materialWithoutTextures = pImage.isNull();
// uvs always must be added wheater we have texture or not // uvs always must be added wheater we have texture or not
uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv"); uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv");
uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue())); uvCoordinatesList.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));
uvCoordinates.add(new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue())); uvCoordinatesList.add(new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue()));
uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue())); uvCoordinatesList.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));
} }
int matNr = ((Number) mFace.getFieldValue("mat_nr")).intValue();
Integer materialNumber = Integer.valueOf(materialWithoutTextures ? -1 * matNr - 1 : matNr); Integer materialNumber = Integer.valueOf(materialWithoutTextures ? -1 * matNr - 1 : matNr);
List<Integer> indexList = meshesMap.get(materialNumber); List<Integer> indexList = meshesMap.get(materialNumber);
if (indexList == null) { if (indexList == null) {
@ -216,9 +219,9 @@ public class MeshHelper extends AbstractBlenderHelper {
if (v4 > 0) { if (v4 > 0) {
if (uvs != null) { if (uvs != null) {
uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue())); uvCoordinatesList.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));
uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue())); uvCoordinatesList.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));
uvCoordinates.add(new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue())); uvCoordinatesList.add(new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue()));
} }
this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap); this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);
indexList.add(vertexList.size()); indexList.add(vertexList.size());
@ -266,11 +269,9 @@ public class MeshHelper extends AbstractBlenderHelper {
// reading materials // reading materials
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
Material[] materials = null; MaterialContext[] materials = null;
Material[] nonTexturedMaterials = null;
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
materials = materialHelper.getMaterials(structure, blenderContext); materials = materialHelper.getMaterials(structure, blenderContext);
nonTexturedMaterials = materials == null ? null : new Material[materials.length];// fill it when needed
} }
// creating the result meshes // creating the result meshes
@ -291,22 +292,19 @@ public class MeshHelper extends AbstractBlenderHelper {
VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal); VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal);
normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(normalsBuffer.getData())); normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(normalsBuffer.getData()));
VertexBuffer uvCoordsBuffer = null;
if (uvCoordinates != null) {
uvCoordsBuffer = new VertexBuffer(Type.TexCoord);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float,
BufferUtils.createFloatBuffer(uvCoordinates.toArray(new Vector2f[uvCoordinates.size()])));
}
//reading custom properties //reading custom properties
Properties properties = this.loadProperties(structure, blenderContext); Properties properties = this.loadProperties(structure, blenderContext);
// generating meshes // generating meshes
//FloatBuffer verticesColorsBuffer = this.createFloatBuffer(verticesColors); //FloatBuffer verticesColorsBuffer = this.createFloatBuffer(verticesColors);
ByteBuffer verticesColorsBuffer = createByteBuffer(verticesColors); ByteBuffer verticesColorsBuffer = this.createByteBuffer(verticesColors);
verticesAmount = vertexList.size(); verticesAmount = vertexList.size();
Map<Mesh, Integer> meshToMAterialMap = new HashMap<Mesh, Integer>(meshesMap.size());
for (Entry<Integer, List<Integer>> meshEntry : meshesMap.entrySet()) { for (Entry<Integer, List<Integer>> meshEntry : meshesMap.entrySet()) {
//key is the material index (or -1 if the material has no texture)
//value is a list of vertex indices
Mesh mesh = new Mesh(); Mesh mesh = new Mesh();
meshToMAterialMap.put(mesh, meshEntry.getKey());
// creating vertices indices for this mesh // creating vertices indices for this mesh
List<Integer> indexList = meshEntry.getValue(); List<Integer> indexList = meshEntry.getValue();
@ -339,75 +337,31 @@ public class MeshHelper extends AbstractBlenderHelper {
// creating the result // creating the result
Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh); Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);
if (materials != null) {
int materialNumber = meshEntry.getKey().intValue();
Material material;
if (materialNumber >= 0) {
material = materials[materialNumber];
if (materialNumberToTexture.containsKey(Integer.valueOf(materialNumber))) {
if (material.getMaterialDef().getAssetName().contains("Lighting")) {
if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_DIFFUSE)) {
material = material.clone();
material.setTexture(MaterialHelper.TEXTURE_TYPE_DIFFUSE,
materialNumberToTexture.get(Integer.valueOf(materialNumber)));
}
} else {
if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_COLOR)) {
material = material.clone();
material.setTexture(MaterialHelper.TEXTURE_TYPE_COLOR,
materialNumberToTexture.get(Integer.valueOf(materialNumber)));
}
}
}
} else {
materialNumber = -1 * (materialNumber + 1);
if (nonTexturedMaterials[materialNumber] == null) {
nonTexturedMaterials[materialNumber] = materialHelper.getNonTexturedMaterial(materials[materialNumber],
TextureHelper.TEX_IMAGE);
}
material = nonTexturedMaterials[materialNumber];
}
geometry.setMaterial(material);
if (material.isTransparent()) {
geometry.setQueueBucket(Bucket.Transparent);
}
} else {
geometry.setMaterial(blenderContext.getDefaultMaterial());
}
if (properties != null && properties.getValue() != null) { if (properties != null && properties.getValue() != null) {
geometry.setUserData("properties", properties); geometry.setUserData("properties", properties);
} }
geometries.add(geometry); geometries.add(geometry);
} }
//applying uvCoordinates for all the meshes //store the data in blender context before applying the material
if (uvCoordsBuffer != null) { blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);
for (Geometry geom : geometries) { blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext);
geom.getMesh().setBuffer(uvCoordsBuffer);
}
} else {
Map<Material, List<Geometry>> materialMap = new HashMap<Material, List<Geometry>>();
for (Geometry geom : geometries) {
Material material = geom.getMaterial();
List<Geometry> geomsWithCommonMaterial = materialMap.get(material);
if (geomsWithCommonMaterial == null) {
geomsWithCommonMaterial = new ArrayList<Geometry>();
materialMap.put(material, geomsWithCommonMaterial);
}
geomsWithCommonMaterial.add(geom);
//apply materials only when all geometries are in place
if(materials != null) {
for(Geometry geometry : geometries) {
int materialNumber = meshToMAterialMap.get(geometry.getMesh()).intValue();
boolean noTextures = false;
if(materialNumber < 0) {
materialNumber = -1 * (materialNumber + 1);
noTextures = true;
} }
for (Entry<Material, List<Geometry>> entry : materialMap.entrySet()) { MaterialContext materialContext = materials[materialNumber];
MaterialContext materialContext = blenderContext.getMaterialContext(entry.getKey()); materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), noTextures, uvCoordinates.get(Integer.valueOf(materialNumber)), blenderContext);
if (materialContext != null && materialContext.getTexturesCount() > 0) {
VertexBuffer coords = UVCoordinatesGenerator.generateUVCoordinates(materialContext.getUvCoordinatesType(),
materialContext.getProjectionType(), materialContext.getTextureDimension(),
materialContext.getProjection(0), entry.getValue());
//setting the coordinates inside the mesh context
for (Geometry geometry : entry.getValue()) {
meshContext.addUVCoordinates(geometry, coords);
}
} }
} else {
for(Geometry geometry : geometries) {
geometry.setMaterial(blenderContext.getDefaultMaterial());
} }
} }
@ -420,8 +374,6 @@ public class MeshHelper extends AbstractBlenderHelper {
} }
} }
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);
blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext);
return geometries; return geometries;
} }

@ -0,0 +1,315 @@
package com.jme3.scene.plugins.blender.textures;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
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;
/**
* A class constaining the colorband data.
*
* @author Marcin Roguski (Kaelthas)
*/
public class ColorBand {
private static final Logger LOGGER = Logger.getLogger(ColorBand.class.getName());
// 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;
private int cursorsAmount, ipoType;
private ColorBandData[] data;
/**
* Constructor. Loads the data from the given structure.
*
* @param cbdataStructure
* the colorband structure
*/
@SuppressWarnings("unchecked")
public ColorBand(Structure tex, BlenderContext blenderContext) {
int flag = ((Number) tex.getFieldValue("flag")).intValue();
if ((flag & GeneratedTexture.TEX_COLORBAND) != 0) {
Pointer pColorband = (Pointer) tex.getFieldValue("coba");
try {
Structure colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0);
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));
}
} catch (BlenderFileException e) {
LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage());
}
}
}
/**
* This method determines if the colorband has any transparencies or is not
* transparent at all.
*
* @return <b>true</b> if the colorband has transparencies and <b>false</b>
* otherwise
*/
public boolean hasTransparencies() {
if (data != null) {
for (ColorBandData colorBandData : data) {
if (colorBandData.a < 1.0f) {
return true;
}
}
}
return false;
}
/**
* This method computes the values of the colorband.
*
* @return an array of 1001 elements and each element is float[4] object
* containing rgba values
*/
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
for (int i = 0; i < result.length; ++i) {
result[i][0] = data[0].r;
result[i][1] = data[0].g;
result[i][2] = data[0].b;
result[i][3] = data[0].a;
}
} else {
int currentCursor = 0;
ColorBandData currentData = data[0];
ColorBandData nextData = data[0];
switch (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 = data[currentCursor++];
if (currentCursor < data.length) {
nextData = data[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 < data.length; ++i) {
cbDataMap.put(Integer.valueOf(i), data[i]);
}
if (data[0].pos == 0) {
cbDataMap.put(Integer.valueOf(-1), data[0]);
} else {
ColorBandData cbData = data[0].clone();
cbData.pos = 0;
cbDataMap.put(Integer.valueOf(-1), cbData);
cbDataMap.put(Integer.valueOf(-2), cbData);
}
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();
cbData.pos = 1000;
cbDataMap.put(Integer.valueOf(data.length), cbData);
cbDataMap.put(Integer.valueOf(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);
f = FastMath.clamp(f, 0.0f, 1.0f);
} else {
f = 0.0f;
}
this.getIpoData(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 = data[currentCursor++];
if (currentCursor < data.length) {
nextData = data[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 = data[currentCursor++];
if (currentCursor < data.length) {
nextData = data[currentCursor];
}
}
}
break;
default:
throw new IllegalStateException("Unknown interpolation type: " + ipoType);
}
}
}
return result;
}
/**
* This method returns the data for either B-spline of Cardinal
* interpolation.
*
* @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(float d, float[] ipoFactors) {
float d2 = d * d;
float d3 = d2 * d;
if (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 (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!");
}
}
/**
* Class to store the single colorband cursor data.
*
* @author Marcin Roguski (Kaelthas)
*/
private 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 + "]";
}
}
}

@ -0,0 +1,367 @@
package com.jme3.scene.plugins.blender.textures;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import jme3tools.converters.ImageToAwt;
import com.jme3.math.Vector2f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType;
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.io.PixelIOFactory;
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D;
/**
* This class represents a texture that is defined for the material. It can be
* made of several textures (both 2D and 3D) that are merged together and
* returned as a single texture.
*
* @author Marcin Roguski (Kaelthas)
*/
public class CombinedTexture {
/** The data for each of the textures. */
private List<TextureData> textureDatas = new ArrayList<TextureData>();
/** The result texture. */
private Texture resultTexture;
/** The UV values for the result texture. */
private List<Vector2f> resultUVS;
/**
* This method adds a texture data to the resulting texture.
*
* @param texture
* the source texture
* @param textureBlender
* the texture blender (to mix the texture with its material
* color)
* @param uvCoordinatesType
* the type of UV coordinates
* @param projectionType
* the type of UV coordinates projection (for flat textures)
* @param textureStructure
* the texture sructure
* @param blenderContext
* the blender context
*/
public void add(Texture texture, TextureBlender textureBlender, int uvCoordinatesType, int projectionType, Structure textureStructure, BlenderContext blenderContext) {
if (!(texture instanceof GeneratedTexture) && !(texture instanceof Texture2D)) {
throw new IllegalArgumentException("Unsupported texture type: " + (texture == null ? "null" : texture.getClass()));
}
TextureData textureData = new TextureData();
textureData.texture = texture;
textureData.textureBlender = textureBlender;
textureData.uvCoordinatesType = UVCoordinatesType.valueOf(uvCoordinatesType);
textureData.projectionType = UVProjectionType.valueOf(projectionType);
textureData.textureStructure = textureStructure;
if (this.isWithoutAlpha(textureData, blenderContext)) {
textureDatas.clear();// clear previous textures, they will be
// covered anyway
}
textureDatas.add(textureData);
}
/**
* This method flattens the texture and creates a single result of Texture2D
* type.
*
* @param geometry
* the geometry the texture is created for
* @param geometriesOMA
* the old memory address of the geometries list that the given
* geometry belongs to (needed for bounding box creation)
* @param userDefinedUVCoordinates
* the UV's defined by user (null or zero length table if none
* were defined)
* @param blenderContext
* the blender context
*/
@SuppressWarnings("unchecked")
public void flatten(Geometry geometry, Long geometriesOMA, List<Vector2f> userDefinedUVCoordinates, BlenderContext blenderContext) {
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
Mesh mesh = geometry.getMesh();
Texture previousTexture = null;
UVCoordinatesType masterUVCoordinatesType = null;
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.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender);
}
if (previousTexture == null) {// the first texture will lead the others to its shape
if (textureData.texture instanceof GeneratedTexture) {
resultTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext);
} else if (textureData.texture instanceof Texture2D) {
resultTexture = textureData.texture;
if (userDefinedUVCoordinates == null || userDefinedUVCoordinates.size() == 0) {
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
resultUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries);
} else {
resultUVS = userDefinedUVCoordinates;
}
}
this.blend(resultTexture, textureData.textureBlender, blenderContext);
previousTexture = resultTexture;
masterUVCoordinatesType = textureData.uvCoordinatesType;
} else {
if (textureData.texture instanceof GeneratedTexture) {
if (!(resultTexture instanceof TriangulatedTexture)) {
resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext);
resultUVS = null;
previousTexture = resultTexture;
}
TriangulatedTexture triangulatedTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext);
triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext);
triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext);
resultTexture = previousTexture = triangulatedTexture;
} else if (textureData.texture instanceof Texture2D) {
if (masterUVCoordinatesType == textureData.uvCoordinatesType && resultTexture instanceof Texture2D) {
this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight());
this.merge((Texture2D) resultTexture, (Texture2D) textureData.texture);
previousTexture = resultTexture;
} else {
if (!(resultTexture instanceof TriangulatedTexture)) {
resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext);
resultUVS = null;
}
// first triangulate the current texture
List<Vector2f> textureUVS = null;
if (textureData.uvCoordinatesType != UVCoordinatesType.TEXCO_UV) {
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
textureUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries);
} else {
textureUVS = userDefinedUVCoordinates;
}
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);
}
}
}
}
if (resultTexture instanceof TriangulatedTexture) {
resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS();
resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture();
}
// setting additional data
resultTexture.setWrap(WrapMode.Repeat);
// the filters are required if generated textures are used because
// otherwise ugly lines appear between the mesh faces
resultTexture.setMagFilter(MagFilter.Nearest);
resultTexture.setMinFilter(MinFilter.NearestNoMipMaps);
}
/**
* This method blends the texture.
*
* @param texture
* the texture to be blended
* @param textureBlender
* blending definition for the texture
* @param blenderContext
* the blender context
*/
private void blend(Texture texture, TextureBlender textureBlender, BlenderContext blenderContext) {
if (texture instanceof TriangulatedTexture) {
((TriangulatedTexture) texture).blend(textureBlender, null, blenderContext);
} else if (texture instanceof Texture2D) {
Image blendedImage = textureBlender.blend(texture.getImage(), null, blenderContext);
texture.setImage(blendedImage);
} else {
throw new IllegalArgumentException("Invalid type for texture to blend!");
}
}
/**
* @return the result texture
*/
public Texture getResultTexture() {
return resultTexture;
}
/**
* @return the result UV coordinates
*/
public List<Vector2f> getResultUVS() {
return resultUVS;
}
/**
* 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) {
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();
for (int x = 0; x < sourceImage.getWidth(); ++x) {
for (int y = 0; y < sourceImage.getHeight(); ++y) {
sourceIO.read(sourceImage, sourcePixel, x, y);
targetIO.read(targetImage, targetPixel, x, y);
targetPixel.merge(sourcePixel);
targetIO.write(targetImage, targetPixel, x, y);
}
}
}
/**
* This method determines if the given texture has no alpha channel.
*
* @param texture
* the texture to check for alpha channel
* @return <b>true</b> if the texture has no alpha channel and <b>false</b>
* otherwise
*/
private boolean isWithoutAlpha(TextureData textureData, BlenderContext blenderContext) {
ColorBand colorBand = new ColorBand(textureData.textureStructure, blenderContext);
if (!colorBand.hasTransparencies()) {
int type = ((Number) textureData.textureStructure.getFieldValue("type")).intValue();
if (type == TextureHelper.TEX_MAGIC) {
return true;
}
if (type == TextureHelper.TEX_VORONOI) {
int voronoiColorType = ((Number) textureData.textureStructure.getFieldValue("vn_coltype")).intValue();
return voronoiColorType != 0;// voronoiColorType == 0:
// intensity, voronoiColorType
// != 0: col1, col2 or col3
}
if (type == TextureHelper.TEX_CLOUDS) {
int sType = ((Number) textureData.textureStructure.getFieldValue("stype")).intValue();
return sType == 1;// sType==0: without colors, sType==1: with
// colors
}
// checking the flat textures for alpha values presence
if (type == TextureHelper.TEX_IMAGE) {
Image image = textureData.texture.getImage();
switch (image.getFormat()) {
case BGR8:
case DXT1:
case Luminance16:
case Luminance16F:
case Luminance32F:
case Luminance8:
case RGB10:
case RGB111110F:
case RGB16:
case RGB16F:
case RGB32F:
case RGB565:
case RGB8:
return true;// these types have no alpha by definition
case ABGR8:
case DXT3:
case DXT5:
case Luminance16Alpha16:
case Luminance16FAlpha16F:
case Luminance8Alpha8:
case RGBA16:
case RGBA16F:
case RGBA32F:
case RGBA8:// with these types it is better to make sure if
// the texture is or is not transparent
PixelInputOutput pixelInputOutput = PixelIOFactory.getPixelIO(image.getFormat());
TexturePixel pixel = new TexturePixel();
for (int x = 0; x < image.getWidth(); ++x) {
for (int y = 0; y < image.getHeight(); ++y) {
pixelInputOutput.read(image, pixel, x, y);
if (pixel.alpha < 1.0f) {
return false;
}
}
}
return true;
}
}
}
return false;
}
/**
* This method scales the given texture to the given size.
*
* @param texture
* the texture to be scaled
* @param width
* new width of the texture
* @param height
* new height of the texture
*/
private void scale(Texture2D texture, int width, int height) {
// first determine if scaling is required
boolean scaleRequired = texture.getImage().getWidth() != width || texture.getImage().getHeight() != height;
if (scaleRequired) {
Image image = texture.getImage();
BufferedImage sourceImage = ImageToAwt.convert(image, false, true, 0);
int sourceWidth = sourceImage.getWidth();
int sourceHeight = sourceImage.getHeight();
BufferedImage targetImage = new BufferedImage(width, height, sourceImage.getType());
Graphics2D g = targetImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(sourceImage, 0, 0, width, height, 0, 0, sourceWidth, sourceHeight, null);
g.dispose();
Image output = new ImageLoader().load(targetImage, false);
image.setWidth(width);
image.setHeight(height);
image.setData(output.getData(0));
image.setFormat(output.getFormat());
}
}
/**
* A simple class to aggregate the texture data (improves code quality).
*
* @author Marcin Roguski (Kaelthas)
*/
private static class TextureData {
/** The texture. */
public Texture texture;
/** The texture blender (to mix the texture with its material color). */
public TextureBlender textureBlender;
/** The type of UV coordinates. */
public UVCoordinatesType uvCoordinatesType;
/** The type of UV coordinates projection (for flat textures). */
public UVProjectionType projectionType;
/** The texture sructure. */
public Structure textureStructure;
}
}

@ -0,0 +1,131 @@
package com.jme3.scene.plugins.blender.textures;
import com.jme3.math.FastMath;
/**
* The data that helps in bytes calculations for the result image.
*
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class DDSTexelData {
/** The colors of the texes. */
private TexturePixel[][] colors;
/** The indexes of the texels. */
private long[] indexes;
/** The alphas of the texels (might be null). */
private float[][] alphas;
/** The indexels of texels alpha values (might be null). */
private long[] alphaIndexes;
/** The counter of texel x column. */
private int xCounter;
/** The counter of texel y row. */
private int yCounter;
/** The width of the image in pixels. */
private int pixelWidth;
/** The height of the image in pixels. */
private int pixelHeight;
/** The total texel count. */
private int xTexelCount;
/**
* Constructor. Allocates the required memory. Initializes variables.
*
* @param textelsCount
* the total count of the texels
* @param pixelWidth
* the width of the image in pixels
* @param pixelHeight
* the height of the image in pixels
* @param isAlpha
* indicates if the memory for alpha values should be
* allocated
*/
public DDSTexelData(int textelsCount, int pixelWidth, int pixelHeight, boolean isAlpha) {
textelsCount = pixelWidth * pixelHeight >> 4;
this.colors = new TexturePixel[textelsCount][];
this.indexes = new long[textelsCount];
this.xTexelCount = pixelWidth >> 2;
this.yCounter = (pixelHeight >> 2) - 1;// xCounter is 0 for now
this.pixelHeight = pixelHeight;
this.pixelWidth = pixelWidth;
if (isAlpha) {
this.alphas = new float[textelsCount][];
this.alphaIndexes = new long[textelsCount];
}
}
/**
* This method adds a color and indexes for a texel.
*
* @param colors
* the colors of the texel
* @param indexes
* the indexes of the texel
*/
public void add(TexturePixel[] colors, int indexes) {
this.add(colors, indexes, null, 0);
}
/**
* This method adds a color, color indexes and alha values (with their
* indexes) for a texel.
*
* @param colors
* the colors of the texel
* @param indexes
* the indexes of the texel
* @param alphas
* the alpha values
* @param alphaIndexes
* the indexes of the given alpha values
*/
public void add(TexturePixel[] colors, int indexes, float[] alphas, long alphaIndexes) {
int index = yCounter * xTexelCount + xCounter;
this.colors[index] = colors;
this.indexes[index] = indexes;
if (alphas != null) {
this.alphas[index] = alphas;
this.alphaIndexes[index] = alphaIndexes;
}
++this.xCounter;
if (this.xCounter >= this.xTexelCount) {
this.xCounter = 0;
--this.yCounter;
}
}
/**
* This method returns the values of the pixel located on the given
* coordinates on the result image.
*
* @param x
* the x coordinate of the pixel
* @param y
* the y coordinate of the pixel
* @param result
* the table where the result is stored
*/
public void getRGBA8(int x, int y, byte[] result) {
int xTexetlIndex = x % pixelWidth / 4;
int yTexelIndex = y % pixelHeight / 4;
int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex;
TexturePixel[] colors = this.colors[texelIndex];
// coordinates of the pixel in the selected texel
x = x - 4 * xTexetlIndex;// pixels are arranged from left to right
y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start)
int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2);
int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0;
// getting the pixel
int indexMask = colors.length - 1;
int colorIndex = (int) (this.indexes[texelIndex] >> pixelIndexInTexel & indexMask);
float alpha = this.alphas != null ? this.alphas[texelIndex][(int) (this.alphaIndexes[texelIndex] >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha;
result[0] = (byte) (colors[colorIndex].red * 255.0f);
result[1] = (byte) (colors[colorIndex].green * 255.0f);
result[2] = (byte) (colors[colorIndex].blue * 255.0f);
result[3] = (byte) (alpha * 255.0f);
}
}

@ -0,0 +1,151 @@
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.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
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.texture.Image;
import com.jme3.texture.Texture;
/**
* The generated texture loaded from blender file. The texture is not generated
* after being read. This class rather stores all required data and can compute
* a pixel in the required 3D space position.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class GeneratedTexture extends Texture {
// flag values
public static final int TEX_COLORBAND = 1;
public static final int TEX_FLIPBLEND = 2;
public static final int TEX_NEGALPHA = 4;
public static final int TEX_CHECKER_ODD = 8;
public static final int TEX_CHECKER_EVEN = 16;
public static final int TEX_PRV_ALPHA = 32;
public static final int TEX_PRV_NOR = 64;
public static final int TEX_REPEAT_XMIR = 128;
public static final int TEX_REPEAT_YMIR = 256;
public static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR;
/** Material-texture link structure. */
private final Structure mTex;
/** Texture generateo for the specified texture type. */
private final TextureGenerator textureGenerator;
/**
* Constructor. Reads the required data from the 'tex' structure.
*
* @param tex
* the texture structure
* @param mTex
* the material-texture link data structure
* @param textureGenerator
* the generator for the required texture type
* @param blenderContext
* the blender context
*/
public GeneratedTexture(Structure tex, Structure mTex, TextureGenerator textureGenerator, BlenderContext blenderContext) {
this.mTex = mTex;
this.textureGenerator = textureGenerator;
this.textureGenerator.readData(tex, blenderContext);
super.setImage(new GeneratedTextureImage(textureGenerator.getImageFormat()));
}
/**
* This method computes the textyre color/intensity at the specified (u, v,
* s) position in 3D space.
*
* @param pixel
* the pixel where the result is stored
* @param u
* the U factor
* @param v
* the V factor
* @param s
* the S factor
*/
public void getPixel(TexturePixel pixel, float u, float v, float s) {
textureGenerator.getPixel(pixel, u, v, s);
}
/**
* This method triangulates the texture. In the result we get a set of small
* flat textures for each face of the given mesh. This can be later merged
* into one flat texture.
*
* @param mesh
* the mesh we create the texture for
* @param geometriesOMA
* the old memory address of the geometries group that the given
* mesh belongs to (required for bounding box calculations)
* @param coordinatesType
* the types of UV coordinates
* @param blenderContext
* the blender context
* @return triangulated texture
*/
@SuppressWarnings("unchecked")
public TriangulatedTexture triangulate(Mesh mesh, Long geometriesOMA, UVCoordinatesType coordinatesType, BlenderContext blenderContext) {
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
int[] coordinatesSwappingIndexes = new int[] { ((Number) mTex.getFieldValue("projx")).intValue(), ((Number) mTex.getFieldValue("projy")).intValue(), ((Number) mTex.getFieldValue("projz")).intValue() };
List<Vector3f> uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries);
Vector3f[] uvsArray = uvs.toArray(new Vector3f[uvs.size()]);
BoundingBox boundingBox = UVCoordinatesGenerator.getBoundingBox(geometries);
Set<TriangleTextureElement> triangleTextureElements = new TreeSet<TriangleTextureElement>(new Comparator<TriangleTextureElement>() {
@Override
public int compare(TriangleTextureElement o1, TriangleTextureElement o2) {
return o1.faceIndex - o2.faceIndex;
}
});
for (int i = 0; i < mesh.getTriangleCount(); ++i) {
triangleTextureElements.add(new TriangleTextureElement(i, boundingBox, this, uvsArray, blenderContext));
}
return new TriangulatedTexture(triangleTextureElements, blenderContext);
}
@Override
public void setWrap(WrapAxis axis, WrapMode mode) {
}
@Override
public void setWrap(WrapMode mode) {
}
@Override
public WrapMode getWrap(WrapAxis axis) {
return null;
}
@Override
public Type getType() {
return Type.ThreeDimensional;
}
@Override
public Texture createSimpleClone() {
return null;
}
/**
* Private class to give the format of the 'virtual' 3D texture image.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class GeneratedTextureImage extends Image {
public GeneratedTextureImage(Format imageFormat) {
super.format = imageFormat;
}
}
}

@ -1,416 +0,0 @@
/*
* 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();
}
}
}

@ -1,131 +0,0 @@
/*
* 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.file.Structure;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/**
* This class generates the 'clouds' texture.
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorClouds extends TextureGenerator {
// tex->noisetype
protected static final int TEX_NOISESOFT = 0;
protected static final int TEX_NOISEPERL = 1;
// tex->stype
protected static final int TEX_DEFAULT = 0;
protected static final int TEX_COLOR = 1;
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorClouds(NoiseGenerator noiseGenerator) {
super(noiseGenerator);
}
@Override
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
float[] texvec = new float[] { 0, 0, 0 };
TexturePixel texres = new TexturePixel();
// reading the data from the texture structure
float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
int noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
int noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
int noiseType = ((Number) tex.getFieldValue("noisetype")).intValue();
boolean isHard = noiseType != TEX_NOISESOFT;
int sType = ((Number) tex.getFieldValue("stype")).intValue();
int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
float[][] colorBand = this.computeColorband(tex, blenderContext);
Format format = sType == TEX_COLOR || colorBand != null ? Format.RGBA8 : Format.Luminance8;
int bytesPerPixel = sType == TEX_COLOR || colorBand != null ? 4 : 1;
BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
byte[] data = new byte[width * height * depth * bytesPerPixel];
for (int i = -halfW; i < halfW; ++i) {
texvec[0] = wDelta * i;
for (int j = -halfH; j < halfH; ++j) {
texvec[1] = hDelta * j;
for (int k = -halfD; k < halfD; ++k) {
texvec[2] = dDelta * k;
texres.intensity = NoiseGenerator.NoiseFunctions.turbulence(texvec[0], texvec[1], texvec[2], noisesize, noiseDepth, noiseBasis, isHard);
texres.intensity = FastMath.clamp(texres.intensity, 0.0f, 1.0f);
if (colorBand != null) {
int colorbandIndex = (int) (texres.intensity * 1000.0f);
texres.red = colorBand[colorbandIndex][0];
texres.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, texres);
data[index++] = (byte) (texres.red * 255.0f);
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
} else if (sType == TEX_COLOR) {
texres.red = texres.intensity;
texres.green = NoiseGenerator.NoiseFunctions.turbulence(texvec[1], texvec[0], texvec[2], noisesize, noiseDepth, noiseBasis, isHard);
texres.blue = NoiseGenerator.NoiseFunctions.turbulence(texvec[1], texvec[2], texvec[0], noisesize, noiseDepth, noiseBasis, isHard);
texres.green = FastMath.clamp(texres.green, 0.0f, 1.0f);
texres.blue = FastMath.clamp(texres.blue, 0.0f, 1.0f);
this.applyBrightnessAndContrast(bacd, texres);
data[index++] = (byte) (texres.red * 255.0f);
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (255);//1.0f * 255.0f
} else {
this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
data[index++] = (byte) (texres.intensity * 255.0f);
}
}
}
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(format, width, height, depth, dataArray));
}
}

@ -1,106 +0,0 @@
/*
* 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.file.Structure;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/**
* This class generates the 'noise' texture.
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorNoise extends TextureGenerator {
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorNoise(NoiseGenerator noiseGenerator) {
super(noiseGenerator);
}
@Override
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
int val, random, loop;
int noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
TexturePixel texres = new TexturePixel();
int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
float[][] colorBand = this.computeColorband(tex, blenderContext);
Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
int bytesPerPixel = colorBand != null ? 4 : 1;
BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
byte[] data = new byte[width * height * depth * bytesPerPixel];
for (int i = -halfW; i < halfW; ++i) {
for (int j = -halfH; j < halfH; ++j) {
for (int k = -halfD; k < halfD; ++k) {
random = FastMath.rand.nextInt();
val = random & 3;
loop = noisedepth;
while (loop-- != 0) {
random >>= 2;
val *= random & 3;
}
texres.intensity = FastMath.clamp(val, 0.0f, 1.0f);
if (colorBand != null) {
int colorbandIndex = (int) (texres.intensity * 1000.0f);
texres.red = colorBand[colorbandIndex][0];
texres.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, texres);
data[index++] = (byte) (texres.red * 255.0f);
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
} else {
this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
data[index++] = (byte) (texres.intensity * 255.0f);
}
}
}
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(format, width, height, depth, dataArray));
}
}

@ -1,125 +0,0 @@
/*
* 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.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/**
* This class generates the 'stucci' texture.
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorStucci extends TextureGenerator {
protected static final int TEX_NOISESOFT = 0;
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorStucci(NoiseGenerator noiseGenerator) {
super(noiseGenerator);
}
@Override
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue();
float turbul = ((Number) tex.getFieldValue("turbul")).floatValue();
boolean isHard = noisetype != TEX_NOISESOFT;
int stype = ((Number) tex.getFieldValue("stype")).intValue();
if(noisesize<=0.001f) {//the texture goes black if this value is lower than 0.001f
noisesize = 0.001f;
}
float[] texvec = new float[] { 0, 0, 0 };
TexturePixel texres = new TexturePixel();
int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD, noiseValue, ofs;;
float[][] colorBand = this.computeColorband(tex, blenderContext);
Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
int bytesPerPixel = colorBand != null ? 4 : 1;
byte[] data = new byte[width * height * depth * bytesPerPixel];
for (int i = -halfW; i < halfW; ++i) {
texvec[0] = wDelta * i;
for (int j = -halfH; j < halfH; ++j) {
texvec[1] = hDelta * j;
for (int k = -halfD; k < halfD; ++k) {
texvec[2] = dDelta * k;
noiseValue = NoiseGenerator.NoiseFunctions.noise(texvec[0], texvec[1], texvec[2], noisesize, 0, noisebasis, isHard);
ofs = turbul / 200.0f;
if (stype != 0) {
ofs *= noiseValue * noiseValue;
}
texres.intensity = NoiseGenerator.NoiseFunctions.noise(texvec[0], texvec[1], texvec[2] + ofs, noisesize, 0, noisebasis, isHard);
if (colorBand != null) {
int colorbandIndex = (int) (texres.intensity * 1000.0f);
texres.red = colorBand[colorbandIndex][0];
texres.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2];
texres.alpha = colorBand[colorbandIndex][3];
}
if (stype == NoiseGenerator.TEX_WALLOUT) {
texres.intensity = 1.0f - texres.intensity;
}
if (texres.intensity < 0.0f) {
texres.intensity = 0.0f;
}
//no brightness and contrast needed for stucci (it doesn't affect the texture)
if (colorBand != null) {
data[index++] = (byte) (texres.red * 255.0f);
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (texres.alpha * 255.0f);
} else {
data[index++] = (byte) (texres.intensity * 255.0f);
}
}
}
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(format, width, height, depth, dataArray));
}
}

@ -1,171 +0,0 @@
/*
* 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.file.Structure;
import com.jme3.scene.plugins.blender.textures.NoiseGenerator.NoiseMath;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/**
* This class generates the 'voronoi' texture.
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorVoronoi extends TextureGenerator {
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorVoronoi(NoiseGenerator noiseGenerator) {
super(noiseGenerator);
}
@Override
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
float voronoiWeight1 = ((Number) tex.getFieldValue("vn_w1")).floatValue();
float voronoiWeight2 = ((Number) tex.getFieldValue("vn_w2")).floatValue();
float voronoiWeight3 = ((Number) tex.getFieldValue("vn_w3")).floatValue();
float voronoiWeight4 = ((Number) tex.getFieldValue("vn_w4")).floatValue();
float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
float outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue();
float mexp = ((Number) tex.getFieldValue("vn_mexp")).floatValue();
int distm = ((Number) tex.getFieldValue("vn_distm")).intValue();
int voronoiColorType = ((Number) tex.getFieldValue("vn_coltype")).intValue();
TexturePixel texres = new TexturePixel();
float[] texvec = new float[] { 0, 0, 0 };
int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
float[][] colorBand = this.computeColorband(tex, blenderContext);
Format format = voronoiColorType != 0 || colorBand != null ? Format.RGBA8 : Format.Luminance8;
int bytesPerPixel = voronoiColorType != 0 || colorBand != null ? 4 : 1;
BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
float[] da = new float[4], pa = new float[12];
float[] hashPoint = voronoiColorType != 0 ? new float[3] : null;
float[] voronoiWeights = new float[] {FastMath.abs(voronoiWeight1), FastMath.abs(voronoiWeight2),
FastMath.abs(voronoiWeight3), FastMath.abs(voronoiWeight4)};
float weight;
float sc = voronoiWeights[0] + voronoiWeights[1] + voronoiWeights[2] + voronoiWeights[3];
if (sc != 0.0f) {
sc = outscale / sc;
}
byte[] data = new byte[width * height * depth * bytesPerPixel];
for (int i = -halfW; i < halfW; ++i) {
texvec[0] = wDelta * i / noisesize;
for (int j = -halfH; j < halfH; ++j) {
texvec[1] = hDelta * j / noisesize;
for (int k = -halfD; k < halfD; ++k) {
texvec[2] = dDelta * k;
NoiseGenerator.NoiseFunctions.voronoi(texvec[0], texvec[1], texvec[2], da, pa, mexp, distm);
texres.intensity = sc * FastMath.abs(voronoiWeight1 * da[0] + voronoiWeight2 * da[1] + voronoiWeight3 * da[2] + voronoiWeight4 * da[3]);
if(texres.intensity>1.0f) {
texres.intensity = 1.0f;
} else if(texres.intensity<0.0f) {
texres.intensity = 0.0f;
}
if (colorBand != null) {//colorband ALWAYS goes first and covers the color (if set)
int colorbandIndex = (int) (texres.intensity * 1000.0f);
texres.red = colorBand[colorbandIndex][0];
texres.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2];
texres.alpha = colorBand[colorbandIndex][3];
} else if (voronoiColorType != 0) {
texres.red = texres.green = texres.blue = 0.0f;
texres.alpha = 1.0f;
for(int m=0; m<12; m+=3) {
weight = voronoiWeights[m/3];
this.cellNoiseV(pa[m], pa[m + 1], pa[m + 2], hashPoint);
texres.red += weight * hashPoint[0];
texres.green += weight * hashPoint[1];
texres.blue += weight * hashPoint[2];
}
if (voronoiColorType >= 2) {
float t1 = (da[1] - da[0]) * 10.0f;
if (t1 > 1.0f) {
t1 = 1.0f;
}
if (voronoiColorType == 3) {
t1 *= texres.intensity;
} else {
t1 *= sc;
}
texres.red *= t1;
texres.green *= t1;
texres.blue *= t1;
} else {
texres.red *= sc;
texres.green *= sc;
texres.blue *= sc;
}
}
if (voronoiColorType != 0 || colorBand != null) {
this.applyBrightnessAndContrast(bacd, texres);
data[index++] = (byte) (texres.red * 255.0f);
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (texres.alpha * 255.0f);
} else {
this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
data[index++] = (byte) (texres.intensity * 255.0f);
}
}
}
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(format, width, height, depth, dataArray));
}
/**
* Returns a vector/point/color in ca, using point hasharray directly
*/
private void cellNoiseV(float x, float y, float z, float[] hashPoint) {
int xi = (int) Math.floor(x);
int yi = (int) Math.floor(y);
int zi = (int) Math.floor(z);
NoiseMath.hash(xi, yi, zi, hashPoint);
}
}

@ -36,13 +36,12 @@ import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp; import java.awt.image.ColorConvertOp;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import jme3tools.converters.ImageToAwt; import jme3tools.converters.ImageToAwt;
import jme3tools.converters.RGB565;
import com.jme3.asset.AssetManager; import com.jme3.asset.AssetManager;
import com.jme3.asset.AssetNotFoundException; import com.jme3.asset.AssetNotFoundException;
@ -50,7 +49,6 @@ import com.jme3.asset.BlenderKey;
import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.asset.GeneratedTextureKey; import com.jme3.asset.GeneratedTextureKey;
import com.jme3.asset.TextureKey; import com.jme3.asset.TextureKey;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
@ -59,14 +57,15 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialContext; 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;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapMode; import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D; import com.jme3.texture.Texture2D;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
/** /**
@ -92,29 +91,12 @@ public class TextureHelper extends AbstractBlenderHelper {
public static final int TEX_MUSGRAVE = 11; public static final int TEX_MUSGRAVE = 11;
public static final int TEX_VORONOI = 12; public static final int TEX_VORONOI = 12;
public static final int TEX_DISTNOISE = 13; public static final int TEX_DISTNOISE = 13;
public static final int TEX_POINTDENSITY = 14;//v. 25+ public static final int TEX_POINTDENSITY = 14; // v.
public static final int TEX_VOXELDATA = 15;//v. 25+ // 25+
public static final int TEX_VOXELDATA = 15; // v.
// mapto // 25+
public static final int MAP_COL = 1;
public static final int MAP_NORM = 2; private TextureGeneratorFactory textureGeneratorFactory;
public static final int MAP_COLSPEC = 4;
public static final int MAP_COLMIR = 8;
public static final int MAP_VARS = 0xFFF0;
public static final int MAP_REF = 16;
public static final int MAP_SPEC = 32;
public static final int MAP_EMIT = 64;
public static final int MAP_ALPHA = 128;
public static final int MAP_HAR = 256;
public static final int MAP_RAYMIRR = 512;
public static final int MAP_TRANSLU = 1024;
public static final int MAP_AMB = 2048;
public static final int MAP_DISPLACE = 4096;
public static final int MAP_WARP = 8192;
public static final int MAP_LAYER = 16384;
protected NoiseGenerator noiseGenerator;
private Map<Integer, TextureGenerator> textureGenerators = new HashMap<Integer, TextureGenerator>();
/** /**
* This constructor parses the given blender version and stores the result. * This constructor parses the given blender version and stores the result.
@ -127,22 +109,13 @@ public class TextureHelper extends AbstractBlenderHelper {
*/ */
public TextureHelper(String blenderVersion, boolean fixUpAxis) { public TextureHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, false); super(blenderVersion, false);
noiseGenerator = new NoiseGenerator(blenderVersion); textureGeneratorFactory = new TextureGeneratorFactory(blenderVersion);
textureGenerators.put(Integer.valueOf(TEX_BLEND), new TextureGeneratorBlend(noiseGenerator));
textureGenerators.put(Integer.valueOf(TEX_CLOUDS), new TextureGeneratorClouds(noiseGenerator));
textureGenerators.put(Integer.valueOf(TEX_DISTNOISE), new TextureGeneratorDistnoise(noiseGenerator));
textureGenerators.put(Integer.valueOf(TEX_MAGIC), new TextureGeneratorMagic(noiseGenerator));
textureGenerators.put(Integer.valueOf(TEX_MARBLE), new TextureGeneratorMarble(noiseGenerator));
textureGenerators.put(Integer.valueOf(TEX_MUSGRAVE), new TextureGeneratorMusgrave(noiseGenerator));
textureGenerators.put(Integer.valueOf(TEX_NOISE), new TextureGeneratorNoise(noiseGenerator));
textureGenerators.put(Integer.valueOf(TEX_STUCCI), new TextureGeneratorStucci(noiseGenerator));
textureGenerators.put(Integer.valueOf(TEX_VORONOI), new TextureGeneratorVoronoi(noiseGenerator));
textureGenerators.put(Integer.valueOf(TEX_WOOD), new TextureGeneratorWood(noiseGenerator));
} }
/** /**
* This class returns a texture read from the file or from packed blender data. The returned texture has the name set to the value of * This class returns a texture read from the file or from packed blender
* its blender type. * data. The returned texture has the name set to the value of its blender
* type.
* *
* @param tex * @param tex
* texture structure filled with data * texture structure filled with data
@ -150,17 +123,15 @@ public class TextureHelper extends AbstractBlenderHelper {
* the blender context * the blender context
* @return the texture that can be used by JME engine * @return the texture that can be used by JME engine
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted * this exception is thrown when the blend file structure is
* somehow invalid or corrupted
*/ */
public Texture getTexture(Structure tex, BlenderContext blenderContext) throws BlenderFileException { public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException {
Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (result != null) { if (result != null) {
return result; return result;
} }
int type = ((Number) tex.getFieldValue("type")).intValue(); int type = ((Number) tex.getFieldValue("type")).intValue();
int width = blenderContext.getBlenderKey().getGeneratedTextureWidth();
int height = blenderContext.getBlenderKey().getGeneratedTextureHeight();
int depth = blenderContext.getBlenderKey().getGeneratedTextureDepth();
switch (type) { switch (type) {
case TEX_IMAGE:// (it is first because probably this will be most commonly used) case TEX_IMAGE:// (it is first because probably this will be most commonly used)
@ -168,6 +139,7 @@ public class TextureHelper extends AbstractBlenderHelper {
if (pImage.isNotNull()) { if (pImage.isNotNull()) {
Structure image = pImage.fetchData(blenderContext.getInputStream()).get(0); Structure image = pImage.fetchData(blenderContext.getInputStream()).get(0);
result = this.getTextureFromImage(image, blenderContext); result = this.getTextureFromImage(image, blenderContext);
this.applyColorbandAndColorFactors(tex, result.getImage(), blenderContext);
} }
break; break;
case TEX_CLOUDS: case TEX_CLOUDS:
@ -180,8 +152,7 @@ public class TextureHelper extends AbstractBlenderHelper {
case TEX_MUSGRAVE: case TEX_MUSGRAVE:
case TEX_VORONOI: case TEX_VORONOI:
case TEX_DISTNOISE: case TEX_DISTNOISE:
TextureGenerator textureGenerator = textureGenerators.get(Integer.valueOf(type)); result = new GeneratedTexture(tex, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext);
result = textureGenerator.generate(tex, width, height, depth, blenderContext);
break; break;
case TEX_NONE:// No texture, do nothing case TEX_NONE:// No texture, do nothing
break; break;
@ -192,7 +163,7 @@ public class TextureHelper extends AbstractBlenderHelper {
LOGGER.warning("Voxel data texture loading currently not supported!"); LOGGER.warning("Voxel data texture loading currently not supported!");
break; break;
case TEX_PLUGIN: case TEX_PLUGIN:
case TEX_ENVMAP:// TODO: implement envmap texture case TEX_ENVMAP:
LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, tex.getName() }); LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, tex.getName() });
break; break;
default: default:
@ -210,89 +181,9 @@ public class TextureHelper extends AbstractBlenderHelper {
return result; return result;
} }
/**
* This method merges the given textures. The result texture has no alpha
* factor (is always opaque).
*
* @param sources
* the textures to be merged
* @param materialContext
* the context of the material
* @return merged textures
*/
public Texture mergeTextures(List<Texture> sources, MaterialContext materialContext) {
Texture result = null;
if(sources!=null && sources.size()>0) {
if(sources.size() == 1) {
return sources.get(0);//just return the texture
}
//checking the sizes of the textures (tehy should perfectly match)
int lastTextureWithoutAlphaIndex = 0;
int width = sources.get(0).getImage().getWidth();
int height = sources.get(0).getImage().getHeight();
int depth = sources.get(0).getImage().getDepth();
for(Texture source : sources) {
if(source.getImage().getWidth() != width) {
throw new IllegalArgumentException("The texture " + source.getName() + " has invalid width! It should be: " + width + '!');
}
if(source.getImage().getHeight() != height) {
throw new IllegalArgumentException("The texture " + source.getName() + " has invalid height! It should be: " + height + '!');
}
if(source.getImage().getDepth() != depth) {
throw new IllegalArgumentException("The texture " + source.getName() + " has invalid depth! It should be: " + depth + '!');
}
//support for more formats is not necessary at the moment
if(source.getImage().getFormat()!=Format.RGB8 && source.getImage().getFormat()!=Format.BGR8) {
++lastTextureWithoutAlphaIndex;
}
}
if(depth==0) {
depth = 1;
}
//remove textures before the one without alpha (they will be covered anyway)
if(lastTextureWithoutAlphaIndex > 0 && lastTextureWithoutAlphaIndex<sources.size()-1) {
sources = sources.subList(lastTextureWithoutAlphaIndex, sources.size()-1);
}
int pixelsAmount = width * height * depth;
ByteBuffer data = BufferUtils.createByteBuffer(pixelsAmount * 3);
TexturePixel resultPixel = new TexturePixel();
TexturePixel sourcePixel = new TexturePixel();
ColorRGBA diffuseColor = materialContext.getDiffuseColor();
for (int i = 0; i < pixelsAmount; ++i) {
for (int j = 0; j < sources.size(); ++j) {
Image image = sources.get(j).getImage();
ByteBuffer sourceData = image.getData(0);
if(j==0) {
resultPixel.fromColor(diffuseColor);
sourcePixel.fromImage(image.getFormat(), sourceData, i);
resultPixel.merge(sourcePixel);
} else {
sourcePixel.fromImage(image.getFormat(), sourceData, i);
resultPixel.merge(sourcePixel);
}
}
data.put((byte)(255 * resultPixel.red));
data.put((byte)(255 * resultPixel.green));
data.put((byte)(255 * resultPixel.blue));
resultPixel.clear();
}
if(depth==1) {
result = new Texture2D(new Image(Format.RGB8, width, height, data));
} else {
ArrayList<ByteBuffer> arrayData = new ArrayList<ByteBuffer>(1);
arrayData.add(data);
result = new Texture3D(new Image(Format.RGB8, width, height, depth, arrayData));
}
}
return result;
}
/** /**
* This method converts the given texture into normal-map texture. * This method converts the given texture into normal-map texture.
*
* @param source * @param source
* the source texture * the source texture
* @param strengthFactor * @param strengthFactor
@ -337,8 +228,166 @@ public class TextureHelper extends AbstractBlenderHelper {
} }
/** /**
* This method returns the height represented by the specified pixel in the given texture. * This method decompresses the given image. If the given image is already
* The given texture should be a height-map. * decompressed nothing happens and it is simply returned.
*
* @param image
* the image to decompress
* @return the decompressed image
*/
public Image decompress(Image image) {
byte[] bytes = null;
TexturePixel[] colors = null;
ByteBuffer data = image.getData(0);// TODO: support decompression of all data 'layers'
data.rewind();
Format format = image.getFormat();
DDSTexelData texelData = new DDSTexelData(data.remaining() / (format.getBitsPerPixel() * 2), image.getWidth(), image.getHeight(), format != Format.DXT1);
switch (format) {// TODO: DXT1A
case DXT1:// BC1
bytes = new byte[image.getWidth() * image.getHeight() * 4];
colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
while (data.hasRemaining()) {
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
bytes = new byte[image.getWidth() * image.getHeight() * 4];
colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
while (data.hasRemaining()) {
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
bytes = new byte[image.getWidth() * image.getHeight() * 4];
colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
float[] alphas = new float[8];
while (data.hasRemaining()) {
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:
LOGGER.fine("Unsupported decompression format.");
}
if (bytes != null) {// writing the data to the result table
byte[] pixelBytes = new byte[4];
for (int i = 0; i < image.getWidth(); ++i) {
for (int j = 0; j < image.getHeight(); ++j) {
texelData.getRGBA8(i, j, pixelBytes);
bytes[(j * image.getWidth() + i) * 4] = pixelBytes[0];
bytes[(j * image.getWidth() + i) * 4 + 1] = pixelBytes[1];
bytes[(j * image.getWidth() + i) * 4 + 2] = pixelBytes[2];
bytes[(j * image.getWidth() + i) * 4 + 3] = pixelBytes[3];
}
}
// TODO: think of other image formats (ie. RGB if the texture has no
// alpha values)
return new Image(Format.RGBA8, image.getWidth(), image.getHeight(), BufferUtils.createByteBuffer(bytes));
}
return image;
}
/**
* This method returns the height represented by the specified pixel in the
* given texture. The given texture should be a height-map.
*
* @param image * @param image
* the height-map texture * the height-map texture
* @param x * @param x
@ -362,10 +411,15 @@ public class TextureHelper extends AbstractBlenderHelper {
} }
/** /**
* This method transforms given vector's coordinates into ARGB color (A is always = 255). * This method transforms given vector's coordinates into ARGB color (A is
* @param x X factor of the vector * always = 255).
* @param y Y factor of the vector *
* @param z Z factor of the vector * @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 * @return color representation of the given vector
*/ */
protected int vectorToColor(float x, float y, float z) { protected int vectorToColor(float x, float y, float z) {
@ -376,7 +430,8 @@ public class TextureHelper extends AbstractBlenderHelper {
} }
/** /**
* This class returns a texture read from the file or from packed blender data. * This class returns a texture read from the file or from packed blender
* data.
* *
* @param image * @param image
* image structure filled with data * image structure filled with data
@ -384,7 +439,8 @@ public class TextureHelper extends AbstractBlenderHelper {
* the blender context * the blender context
* @return the texture that can be used by JME engine * @return the texture that can be used by JME engine
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted * this exception is thrown when the blend file structure is
* somehow invalid or corrupted
*/ */
public Texture getTextureFromImage(Structure image, BlenderContext blenderContext) throws BlenderFileException { public Texture getTextureFromImage(Structure image, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", image.getOldMemoryAddress()); LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", image.getOldMemoryAddress());
@ -421,6 +477,57 @@ public class TextureHelper extends AbstractBlenderHelper {
return result; return result;
} }
/**
* This method applies the colorband and color factors to image type
* textures. If there is no colorband defined for the texture or the color
* factors are all equal to 1.0f then no changes are made.
*
* @param tex
* the texture structure
* @param image
* the image that will be altered if necessary
* @param blenderContext
* the blender context
*/
private void applyColorbandAndColorFactors(Structure tex, Image image, BlenderContext blenderContext) {
float rfac = ((Number) tex.getFieldValue("rfac")).floatValue();
float gfac = ((Number) tex.getFieldValue("gfac")).floatValue();
float bfac = ((Number) tex.getFieldValue("bfac")).floatValue();
float[][] colorBand = new ColorBand(tex, blenderContext).computeValues();
if (colorBand != null) {
TexturePixel pixel = new TexturePixel();
PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat());
for (int x = 0; x < image.getWidth(); ++x) {
for (int y = 0; y < image.getHeight(); ++y) {
imageIO.read(image, pixel, x, y);
int colorbandIndex = (int) (pixel.alpha * 1000.0f);
pixel.red = colorBand[colorbandIndex][0] * rfac;
pixel.green = colorBand[colorbandIndex][1] * gfac;
pixel.blue = colorBand[colorbandIndex][2] * bfac;
pixel.alpha = colorBand[colorbandIndex][3];
imageIO.write(image, pixel, x, y);
}
}
} else if (rfac != 1.0f || gfac != 1.0f || bfac != 1.0f) {
TexturePixel pixel = new TexturePixel();
PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat());
for (int x = 0; x < image.getWidth(); ++x) {
for (int y = 0; y < image.getHeight(); ++y) {
imageIO.read(image, pixel, x, y);
pixel.red *= rfac;
pixel.green *= gfac;
pixel.blue *= bfac;
imageIO.write(image, pixel, x, y);
}
}
}
}
/** /**
* This method loads the textre from outside the blend file. * This method loads the textre from outside the blend file.
* *
@ -447,7 +554,8 @@ public class TextureHelper extends AbstractBlenderHelper {
int idx = blenderKey.getName().lastIndexOf('/'); int idx = blenderKey.getName().lastIndexOf('/');
String blenderAssetFolder = blenderKey.getName().substring(0, idx != -1 ? idx : 0); String blenderAssetFolder = blenderKey.getName().substring(0, idx != -1 ? idx : 0);
assetNames.add(blenderAssetFolder + '/' + relativePath); assetNames.add(blenderAssetFolder + '/' + relativePath);
} else {//use every path from the asset name to the root (absolute path) } else {// use every path from the asset name to the root (absolute
// path)
String[] paths = name.split("\\/"); String[] paths = name.split("\\/");
StringBuilder sb = new StringBuilder(paths[paths.length - 1]);// the asset name StringBuilder sb = new StringBuilder(paths[paths.length - 1]);// the asset name
assetNames.add(paths[paths.length - 1]); assetNames.add(paths[paths.length - 1]);
@ -466,7 +574,8 @@ public class TextureHelper extends AbstractBlenderHelper {
key.setGenerateMips(true); key.setGenerateMips(true);
key.setAsCube(false); key.setAsCube(false);
result = assetManager.loadTexture(key); result = assetManager.loadTexture(key);
break;//if no exception is thrown then accept the located asset and break the loop break;// if no exception is thrown then accept the located asset
// and break the loop
} catch (AssetNotFoundException e) { } catch (AssetNotFoundException e) {
LOGGER.fine(e.getLocalizedMessage()); LOGGER.fine(e.getLocalizedMessage());
} }

@ -2,10 +2,6 @@ package com.jme3.scene.plugins.blender.textures;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.texture.Image.Format;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* The class that stores the pixel values of a texture. * The class that stores the pixel values of a texture.
@ -13,8 +9,6 @@ import java.util.logging.Logger;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TexturePixel implements Cloneable { public class TexturePixel implements Cloneable {
private static final Logger LOGGER = Logger.getLogger(TexturePixel.class.getName());
/** The pixel data. */ /** The pixel data. */
public float intensity, red, green, blue, alpha; public float intensity, red, green, blue, alpha;
@ -64,6 +58,35 @@ public class TexturePixel implements Cloneable {
this.blue = b; this.blue = b;
} }
/**
* Copies the values from the given values.
*
* @param a
* the alpha value
* @param r
* the red value
* @param g
* the green value
* @param b
* the blue value
*/
public void fromARGB8(byte a, byte r, byte g, byte b) {
this.alpha = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f;
this.red = r >= 0 ? r / 255.0f : 1.0f - ~r / 255.0f;
this.green = g >= 0 ? g / 255.0f : 1.0f - ~g / 255.0f;
this.blue = b >= 0 ? b / 255.0f : 1.0f - ~b / 255.0f;
}
/**
* Copies the intensity from the given value.
*
* @param intensity
* the intensity value
*/
public void fromIntensity(byte intensity) {
this.intensity = intensity >= 0 ? intensity / 255.0f : 1.0f - ~intensity / 255.0f;
}
/** /**
* Copies the values from the given integer that stores the ARGB8 data. * Copies the values from the given integer that stores the ARGB8 data.
* *
@ -72,79 +95,13 @@ public class TexturePixel implements Cloneable {
*/ */
public void fromARGB8(int argb8) { public void fromARGB8(int argb8) {
byte pixelValue = (byte) ((argb8 & 0xFF000000) >> 24); byte pixelValue = (byte) ((argb8 & 0xFF000000) >> 24);
this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
pixelValue = (byte) ((argb8 & 0xFF0000) >> 16); pixelValue = (byte) ((argb8 & 0xFF0000) >> 16);
this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
pixelValue = (byte) ((argb8 & 0xFF00) >> 8); pixelValue = (byte) ((argb8 & 0xFF00) >> 8);
this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
pixelValue = (byte) (argb8 & 0xFF); pixelValue = (byte) (argb8 & 0xFF);
this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
}
/**
* Copies the data from the given image.
*
* @param imageFormat
* the image format
* @param data
* the image data
* @param pixelIndex
* the index of the required pixel
*/
public void fromImage(Format imageFormat, ByteBuffer data, int pixelIndex) {
int firstByteIndex;
byte pixelValue;
switch (imageFormat) {
case ABGR8:
firstByteIndex = pixelIndex << 2;
pixelValue = data.get(firstByteIndex);
this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 1);
this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 2);
this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 3);
this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
break;
case RGBA8:
firstByteIndex = pixelIndex << 2;
pixelValue = data.get(firstByteIndex);
this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 1);
this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 2);
this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 3);
this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
break;
case BGR8:
firstByteIndex = pixelIndex * 3;
pixelValue = data.get(firstByteIndex);
this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 1);
this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 2);
this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
this.alpha = 1.0f;
break;
case RGB8:
firstByteIndex = pixelIndex * 3;
pixelValue = data.get(firstByteIndex);
this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 1);
this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
pixelValue = data.get(firstByteIndex + 2);
this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
this.alpha = 1.0f;
break;
case Luminance8:
pixelValue = data.get(pixelIndex);
this.intensity = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
break;
default:
LOGGER.log(Level.FINEST, "Unknown type of texture: {0}. Black pixel used!", imageFormat);
this.intensity = this.blue = this.red = this.green = this.alpha = 0.0f;
}
} }
/** /**
@ -191,6 +148,41 @@ public class TexturePixel implements Cloneable {
return result; return result;
} }
/**
* @return the intensity of the pixel
*/
public byte getInt() {
return (byte) (this.intensity * 255.0f);
}
/**
* @return the alpha value of the pixel
*/
public byte getA8() {
return (byte) (this.alpha * 255.0f);
}
/**
* @return the alpha red of the pixel
*/
public byte getR8() {
return (byte) (this.red * 255.0f);
}
/**
* @return the green value of the pixel
*/
public byte getG8() {
return (byte) (this.green * 255.0f);
}
/**
* @return the blue value of the pixel
*/
public byte getB8() {
return (byte) (this.blue * 255.0f);
}
/** /**
* Merges two pixels (adds the values of each color). * Merges two pixels (adds the values of each color).
* *
@ -202,7 +194,7 @@ public class TexturePixel implements Cloneable {
this.red = oneMinusAlpha * this.red + pixel.alpha * pixel.red; this.red = oneMinusAlpha * this.red + pixel.alpha * pixel.red;
this.green = oneMinusAlpha * this.green + pixel.alpha * pixel.green; this.green = oneMinusAlpha * this.green + pixel.alpha * pixel.green;
this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue; this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue;
// alpha should be always 1.0f as a result this.alpha = (this.alpha + pixel.alpha) * 0.5f;
} }
/** /**

@ -0,0 +1,721 @@
package com.jme3.scene.plugins.blender.textures;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import jme3tools.converters.ImageToAwt;
import com.jme3.bounding.BoundingBox;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
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.Texture2D;
import com.jme3.util.BufferUtils;
/**
* This texture holds a set of images for each face in the specified mesh. It
* helps to flatten 3D texture, merge 3D and 2D textures and merge 2D textures
* with different UV coordinates.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class TriangulatedTexture extends Texture {
/** The result image format. */
private Format format;
/** The collection of images for each face. */
private Collection<TriangleTextureElement> faceTextures;
/**
* The maximum texture size (width/height). This is taken from the blender
* key.
*/
private int maxTextureSize;
/** The result texture. */
private Texture2D resultTexture;
/** The result texture's UV coordinates. */
private List<Vector2f> resultUVS;
/**
* This method triangulates the given flat texture. The given texture is not
* changed.
*
* @param texture2d
* the texture to be triangulated
* @param uvs
* the UV coordinates for each face
*/
public TriangulatedTexture(Texture2D texture2d, List<Vector2f> uvs, BlenderContext blenderContext) {
maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize();
faceTextures = new TreeSet<TriangleTextureElement>(new Comparator<TriangleTextureElement>() {
@Override
public int compare(TriangleTextureElement o1, TriangleTextureElement o2) {
return o1.faceIndex - o2.faceIndex;
}
});
int facesCount = uvs.size() / 3;
for (int i = 0; i < facesCount; ++i) {
faceTextures.add(new TriangleTextureElement(i, texture2d, uvs));
}
this.format = texture2d.getImage().getFormat();
}
/**
* Constructor that simply stores precalculated images.
*
* @param faceTextures
* a collection of images for the mesh's faces
* @param blenderContext
* the blender context
*/
public TriangulatedTexture(Collection<TriangleTextureElement> faceTextures, BlenderContext blenderContext) {
maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize();
this.faceTextures = faceTextures;
for (TriangleTextureElement faceTextureElement : faceTextures) {
if (format == null) {
format = faceTextureElement.image.getFormat();
} else if (format != faceTextureElement.image.getFormat()) {
throw new IllegalArgumentException("Face texture element images MUST have the same image format!");
}
}
}
/**
* This method blends the each image using the given blender and taking base
* texture into consideration.
*
* @param textureBlender
* the texture blender that holds the blending definition
* @param baseTexture
* the texture that is 'below' the current texture (can be null)
* @param blenderContext
* the blender context
*/
public void blend(TextureBlender textureBlender, TriangulatedTexture baseTexture, BlenderContext blenderContext) {
Format newFormat = null;
for (TriangleTextureElement triangleTextureElement : this.faceTextures) {
Image baseImage = baseTexture == null ? null : baseTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image;
triangleTextureElement.image = textureBlender.blend(triangleTextureElement.image, baseImage, blenderContext);
if (newFormat == null) {
newFormat = triangleTextureElement.image.getFormat();
} else if (newFormat != triangleTextureElement.image.getFormat()) {
throw new IllegalArgumentException("Face texture element images MUST have the same image format!");
}
}
this.format = newFormat;
}
/**
* This method alters the images to fit them into UV coordinates of the
* given target texture.
*
* @param targetTexture
* the texture to whose UV coordinates we fit current images
* @param blenderContext
* the blender context
*/
public void castToUVS(TriangulatedTexture targetTexture, BlenderContext blenderContext) {
int[] sourceSize = new int[2], targetSize = new int[2];
ImageLoader imageLoader = new ImageLoader();
for (TriangleTextureElement entry : faceTextures) {
TriangleTextureElement targetFaceTextureElement = targetTexture.getFaceTextureElement(entry.faceIndex);
Vector2f[] dest = targetFaceTextureElement.uv;
// get the sizes of the source and target images
sourceSize[0] = entry.image.getWidth();
sourceSize[1] = entry.image.getHeight();
targetSize[0] = targetFaceTextureElement.image.getWidth();
targetSize[1] = targetFaceTextureElement.image.getHeight();
// create triangle transformation
AffineTransform affineTransform = this.createTransform(entry.uv, dest, sourceSize, targetSize);
// compute the result texture
BufferedImage sourceImage = ImageToAwt.convert(entry.image, false, true, 0);
BufferedImage targetImage = new BufferedImage(targetSize[0], targetSize[1], sourceImage.getType());
Graphics2D g = targetImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(sourceImage, affineTransform, null);
g.dispose();
Image output = imageLoader.load(targetImage, false);
entry.image = output;
entry.uv[0].set(dest[0]);
entry.uv[1].set(dest[1]);
entry.uv[2].set(dest[2]);
}
}
/**
* 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, sourcePixel, x, y);
targetIO.read(targetImage, targetPixel, x, y);
targetPixel.merge(sourcePixel);
targetIO.write(targetImage, 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
* reduce the texture size.
*
* @param rebuild
* a variable that forces texture recomputation (even if it was
* computed vefore)
* @return flat result texture (all images merged into one)
*/
public Texture2D getResultTexture(boolean rebuild) {
if (resultTexture == null || rebuild) {
// sorting the parts by their height (from highest to the lowest)
List<TriangleTextureElement> list = new ArrayList<TriangleTextureElement>(faceTextures);
Collections.sort(list, new Comparator<TriangleTextureElement>() {
@Override
public int compare(TriangleTextureElement o1, TriangleTextureElement o2) {
return o2.image.getHeight() - o1.image.getHeight();
}
});
// arraging the images on the resulting image (calculating the result image width and height)
Set<Integer> duplicatedFaceIndexes = new HashSet<Integer>();
int resultImageHeight = list.get(0).image.getHeight();
int resultImageWidth = 0;
int currentXPos = 0, currentYPos = 0;
Map<TriangleTextureElement, Integer[]> imageLayoutData = new HashMap<TriangleTextureElement, Integer[]>(list.size());
while (list.size() > 0) {
TriangleTextureElement currentElement = list.remove(0);
if (currentXPos + currentElement.image.getWidth() > maxTextureSize) {
currentXPos = 0;
currentYPos = resultImageHeight;
resultImageHeight += currentElement.image.getHeight();
}
Integer[] currentPositions = new Integer[] { currentXPos, currentYPos };
imageLayoutData.put(currentElement, currentPositions);
// removing identical images
for (int i = 0; i < list.size(); ++i) {
if (currentElement.image.equals(list.get(i).image)) {
duplicatedFaceIndexes.add(list.get(i).faceIndex);
imageLayoutData.put(list.remove(i--), currentPositions);
}
}
currentXPos += currentElement.image.getWidth();
resultImageWidth = Math.max(resultImageWidth, currentXPos);
// currentYPos += currentElement.image.getHeight();
// TODO: implement that to compact the result image
// try to add smaller images below the current one
// int remainingHeight = resultImageHeight -
// currentElement.image.getHeight();
// while(remainingHeight > 0) {
// for(int i=list.size() - 1;i>=0;--i) {
//
// }
// }
}
// computing the result UV coordinates
resultUVS = new ArrayList<Vector2f>(imageLayoutData.size() * 3);
for (int i = 0; i < imageLayoutData.size() * 3; ++i) {
resultUVS.add(null);
}
Vector2f[] uvs = new Vector2f[3];
for (Entry<TriangleTextureElement, Integer[]> entry : imageLayoutData.entrySet()) {
Integer[] position = entry.getValue();
entry.getKey().computeFinalUVCoordinates(resultImageWidth, resultImageHeight, position[0], position[1], uvs);
resultUVS.set(entry.getKey().faceIndex * 3, uvs[0]);
resultUVS.set(entry.getKey().faceIndex * 3 + 1, uvs[1]);
resultUVS.set(entry.getKey().faceIndex * 3 + 2, uvs[2]);
}
Image resultImage = new Image(format, resultImageWidth, resultImageHeight, BufferUtils.createByteBuffer(resultImageWidth * resultImageHeight * (format.getBitsPerPixel() >> 3)));
resultTexture = new Texture2D(resultImage);
for (Entry<TriangleTextureElement, Integer[]> entry : imageLayoutData.entrySet()) {
if (!duplicatedFaceIndexes.contains(entry.getKey().faceIndex)) {
this.draw(resultImage, entry.getKey().image, entry.getValue()[0], entry.getValue()[1]);
}
}
}
return resultTexture;
}
/**
* @return the result flat texture
*/
public Texture2D getResultTexture() {
return this.getResultTexture(false);
}
/**
* @return the result texture's UV coordinates
*/
public List<Vector2f> getResultUVS() {
this.getResultTexture();// this is called here to make sure that the result UVS are computed
return resultUVS;
}
/**
* This method returns a single image element for the given face index.
*
* @param faceIndex
* the face index
* @return image element for the required face index
* @throws IllegalStateException
* this exception is thrown if the current image set does not
* contain an image for the given face index
*/
private TriangleTextureElement getFaceTextureElement(int faceIndex) {
for (TriangleTextureElement textureElement : faceTextures) {
if (textureElement.faceIndex == faceIndex) {
return textureElement;
}
}
throw new IllegalStateException("No face texture element found for index: " + faceIndex);
}
/**
* This method draws the source image on the target image starting with the
* specified positions.
*
* @param target
* the target image
* @param source
* the source image
* @param targetXPos
* start X position on the target image
* @param targetYPos
* start Y position on the target image
*/
private void draw(Image target, Image source, int targetXPos, int targetYPos) {
PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(source.getFormat());
PixelInputOutput targetIO = PixelIOFactory.getPixelIO(target.getFormat());
TexturePixel pixel = new TexturePixel();
for (int x = 0; x < source.getWidth(); ++x) {
for (int y = 0; y < source.getHeight(); ++y) {
sourceIO.read(source, pixel, x, y);
targetIO.write(target, pixel, targetXPos + x, targetYPos + y);
}
}
}
/**
* This method creates the affine transform that is used to transform a
* triangle defined by one UV coordinates into a triangle defined by
* different UV's.
*
* @param source
* source UV coordinates
* @param dest
* target UV coordinates
* @param sourceSize
* the width and height of the source image
* @param targetSize
* the width and height of the target image
* @return affine transform to transform one triangle to another
*/
private AffineTransform createTransform(Vector2f[] source, Vector2f[] dest, int[] sourceSize, int[] targetSize) {
float x11 = source[0].getX() * sourceSize[0];
float x12 = source[0].getY() * sourceSize[1];
float x21 = source[1].getX() * sourceSize[0];
float x22 = source[1].getY() * sourceSize[1];
float x31 = source[2].getX() * sourceSize[0];
float x32 = source[2].getY() * sourceSize[1];
float y11 = dest[0].getX() * targetSize[0];
float y12 = dest[0].getY() * targetSize[1];
float y21 = dest[1].getX() * targetSize[0];
float y22 = dest[1].getY() * targetSize[1];
float y31 = dest[2].getX() * targetSize[0];
float y32 = dest[2].getY() * targetSize[1];
float a1 = ((y11 - y21) * (x12 - x32) - (y11 - y31) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22));
float a2 = ((y11 - y21) * (x11 - x31) - (y11 - y31) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21));
float a3 = y11 - a1 * x11 - a2 * x12;
float a4 = ((y12 - y22) * (x12 - x32) - (y12 - y32) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22));
float a5 = ((y12 - y22) * (x11 - x31) - (y12 - y32) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21));
float a6 = y12 - a4 * x11 - a5 * x12;
return new AffineTransform(a1, a4, a2, a5, a3, a6);
}
/**
* A class that represents an image for a single face of the mesh.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */static class TriangleTextureElement {
/** The image for the face. */
public Image image;
/** The UV coordinates for the image. */
public final Vector2f[] uv;
/** The index of the face this image refers to. */
public final int faceIndex;
/**
* Constructor that creates the image element from the given texture and
* UV coordinates (it cuts out the smallest rectasngle possible from the
* given image that will hold the triangle defined by the given UV
* coordinates). After the image is cut out the UV coordinates are
* recalculated to be fit for the new image.
*
* @param faceIndex
* the index of mesh's face this image refers to
* @param texture
* the source texture
* @param uvCoordinates
* the UV coordinates that define the image
*/
public TriangleTextureElement(int faceIndex, Texture2D texture, List<Vector2f> uvCoordinates) {
this.faceIndex = faceIndex;
Image sourceImage = texture.getImage();
uv = new Vector2f[] { uvCoordinates.get(faceIndex * 3).clone(), uvCoordinates.get(faceIndex * 3 + 1).clone(), uvCoordinates.get(faceIndex * 3 + 2).clone() };
float pixelWidth = 1 / (float) sourceImage.getWidth();
float pixelHeight = 1 / (float) sourceImage.getHeight();
// be careful here, floating point operations might cause the
// texture positions to be inapropriate
int[][] texturePosition = new int[3][2];
for (int i = 0; i < texturePosition.length; ++i) {
float x = uv[i].x * sourceImage.getWidth();
float y = uv[i].y * sourceImage.getHeight();
// here is where errors may occur
texturePosition[i][0] = (int) x;
texturePosition[i][1] = (int) y;
// here is where we repair errors :)
if (Math.abs(texturePosition[i][0] - x) > pixelWidth) {
++texturePosition[i][0];
}
if (Math.abs(texturePosition[i][1] - y) > pixelHeight) {
++texturePosition[i][1];
}
}
// calculating the extent of the texture
int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE;
float minUVX = Float.MAX_VALUE, minUVY = Float.MAX_VALUE;
float maxUVX = Float.MIN_VALUE, maxUVY = Float.MIN_VALUE;
for (int i = 0; i < texturePosition.length; ++i) {
minX = Math.min(texturePosition[i][0], minX);
minY = Math.min(texturePosition[i][1], minY);
maxX = Math.max(texturePosition[i][0], maxX);
maxY = Math.max(texturePosition[i][1], maxY);
minUVX = Math.min(uv[i].x, minUVX);
minUVY = Math.min(uv[i].y, minUVY);
maxUVX = Math.max(uv[i].x, maxUVX);
maxUVY = Math.max(uv[i].y, maxUVY);
}
int width = maxX - minX;
int height = maxY - minY;
if (width == 0) {
width = 1;
}
if (height == 0) {
height = 1;
}
// copy the pixel from the texture to the result image
PixelInputOutput pixelReader = PixelIOFactory.getPixelIO(sourceImage.getFormat());
TexturePixel pixel = new TexturePixel();
ByteBuffer data = BufferUtils.createByteBuffer(width * height * 4);
for (int y = minY; y < maxY; ++y) {
for (int x = minX; x < maxX; ++x) {
int xPos = x >= sourceImage.getWidth() ? x - sourceImage.getWidth() : x;
int yPos = y >= sourceImage.getHeight() ? y - sourceImage.getHeight() : y;
pixelReader.read(sourceImage, pixel, xPos, yPos);
data.put(pixel.getR8());
data.put(pixel.getG8());
data.put(pixel.getB8());
data.put(pixel.getA8());
}
}
image = new Image(Format.RGBA8, width, height, data);
// modify the UV values so that they fit the new image
float heightUV = maxUVY - minUVY;
float widthUV = maxUVX - minUVX;
for (int i = 0; i < uv.length; ++i) {
// first translate it to the image borders
uv[i].x -= minUVX;
uv[i].y -= minUVY;
// then scale so that it fills the whole area
uv[i].x /= widthUV;
uv[i].y /= heightUV;
}
}
/**
* Constructor that creates an image element from the 3D texture
* (generated texture). It computes a flat smallest rectangle that can
* hold a (3D) triangle defined by the given UV coordinates. Then it
* defines the image pixels for points in 3D space that define the
* calculated rectangle.
*
* @param faceIndex
* the face index this image refers to
* @param boundingBox
* the bounding box of the mesh
* @param texture
* the texture that allows to compute a pixel value in 3D
* space
* @param uv
* the UV coordinates of the mesh
* @param blenderContext
* the blender context
*/
public TriangleTextureElement(int faceIndex, BoundingBox boundingBox, GeneratedTexture texture, Vector3f[] uv, BlenderContext blenderContext) {
this.faceIndex = faceIndex;
// compute the face vertices from the UV coordinates
float width = boundingBox.getXExtent() * 2;
float height = boundingBox.getYExtent() * 2;
float depth = boundingBox.getZExtent() * 2;
int uvIndex = faceIndex * 3;
Vector3f min = boundingBox.getMin(null);
Vector3f v1 = min.add(uv[uvIndex].x * width, uv[uvIndex].y * height, uv[uvIndex].z * depth);
Vector3f v2 = min.add(uv[uvIndex + 1].x * width, uv[uvIndex + 1].y * height, uv[uvIndex + 1].z * depth);
Vector3f v3 = min.add(uv[uvIndex + 2].x * width, uv[uvIndex + 2].y * height, uv[uvIndex + 2].z * depth);
// get the rectangle envelope for the triangle
RectangleEnvelope envelope = this.getTriangleEnvelope(v1, v2, v3);
// create the result image
Format imageFormat = texture.getImage().getFormat();
int imageWidth = (int) (envelope.width * blenderContext.getBlenderKey().getGeneratedTexturePPU());
int imageHeight = (int) (envelope.height * blenderContext.getBlenderKey().getGeneratedTexturePPU());
ByteBuffer data = BufferUtils.createByteBuffer(imageWidth * imageHeight * (imageFormat.getBitsPerPixel() >> 3));
image = new Image(texture.getImage().getFormat(), imageWidth, imageHeight, data);
// computing the pixels
PixelInputOutput pixelWriter = PixelIOFactory.getPixelIO(imageFormat);
TexturePixel pixel = new TexturePixel();
float[] uvs = new float[3];
Vector3f point = new Vector3f(envelope.min);
Vector3f vecY = new Vector3f();
Vector3f wDelta = new Vector3f(envelope.w).multLocal(1.0f / imageWidth);
Vector3f hDelta = new Vector3f(envelope.h).multLocal(1.0f / imageHeight);
for (int x = 0; x < imageWidth; ++x) {
for (int y = 0; y < imageHeight; ++y) {
this.toTextureUV(boundingBox, point, uvs);
texture.getPixel(pixel, uvs[0], uvs[1], uvs[2]);
pixelWriter.write(image, pixel, x, y);
point.addLocal(hDelta);
}
vecY.addLocal(wDelta);
point.set(envelope.min).addLocal(vecY);
}
// preparing UV coordinates for the flatted texture
this.uv = new Vector2f[3];
this.uv[0] = new Vector2f(FastMath.clamp(v1.subtract(envelope.min).length(), 0, Float.MAX_VALUE) / envelope.height, 0);
Vector3f heightDropPoint = v2.subtract(envelope.w);// w is directed from the base to v2
this.uv[1] = new Vector2f(1, heightDropPoint.subtractLocal(envelope.min).length() / envelope.height);
this.uv[2] = new Vector2f(0, 1);
}
/**
* This method computes the final UV coordinates for the image (after it
* is combined with other images and drawed on the result image).
*
* @param totalImageWidth
* the result image width
* @param totalImageHeight
* the result image height
* @param xPos
* the most left x coordinate of the image
* @param yPos
* the most top y coordinate of the image
* @param result
* a vector where the result is stored
*/
public void computeFinalUVCoordinates(int totalImageWidth, int totalImageHeight, int xPos, int yPos, Vector2f[] result) {
for (int i = 0; i < 3; ++i) {
result[i] = new Vector2f();
result[i].x = xPos / (float) totalImageWidth + this.uv[i].x * (this.image.getWidth() / (float) totalImageWidth);
result[i].y = yPos / (float) totalImageHeight + this.uv[i].y * (this.image.getHeight() / (float) totalImageHeight);
}
}
/**
* This method converts the given point into 3D UV coordinates.
*
* @param boundingBox
* the bounding box of the mesh
* @param point
* the point to be transformed
* @param uvs
* the result UV coordinates
*/
private void toTextureUV(BoundingBox boundingBox, Vector3f point, float[] uvs) {
uvs[0] = (point.x - boundingBox.getCenter().x)/(boundingBox.getXExtent() == 0 ? 1 : boundingBox.getXExtent());
uvs[1] = (point.y - boundingBox.getCenter().y)/(boundingBox.getYExtent() == 0 ? 1 : boundingBox.getYExtent());
uvs[2] = (point.z - boundingBox.getCenter().z)/(boundingBox.getZExtent() == 0 ? 1 : boundingBox.getZExtent());
}
/**
* This method returns an envelope of a minimal rectangle, that is set
* in 3D space, and contains the given triangle.
*
* @param triangle
* the triangle
* @return a rectangle minimum and maximum point and height and width
*/
private RectangleEnvelope getTriangleEnvelope(Vector3f v1, Vector3f v2, Vector3f v3) {
Vector3f h = v3.subtract(v1);// the height of the resulting rectangle
Vector3f temp = v2.subtract(v1);
float field = 0.5f * h.cross(temp).length();// the field of the rectangle: Field = 0.5 * ||h x temp||
if (field <= 0.0f) {
return new RectangleEnvelope(v1);// return single point envelope
}
float cosAlpha = h.dot(temp) / (h.length() * temp.length());// the cosinus of angle betweenh and temp
float triangleHeight = 2 * field / h.length();// the base of the height is the h vector
// now calculate the distance between v1 vertex and the point where
// the above calculated height 'touches' the base line (it can be
// settled outside the h vector)
float x = Math.abs((float) Math.sqrt(FastMath.clamp(temp.lengthSquared() - triangleHeight * triangleHeight, 0, Float.MAX_VALUE))) * Math.signum(cosAlpha);
// now get the height base point
Vector3f xPoint = v1.add(h.normalize().multLocal(x));
// get the minimum point of the envelope
Vector3f min = x < 0 ? xPoint : v1;
if (x < 0) {
h = v3.subtract(min);
} else if (x > h.length()) {
h = xPoint.subtract(min);
}
Vector3f envelopeWidth = v2.subtract(xPoint);
return new RectangleEnvelope(min, envelopeWidth, h);
}
}
/**
* A class that represents a flat rectangle in 3D space that is built on a
* triangle in 3D space.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class RectangleEnvelope {
/** The minimum point of the rectangle. */
public final Vector3f min;
/** The width vector. */
public final Vector3f w;
/** The height vector. */
public final Vector3f h;
/** The width of the rectangle. */
public final float width;
/** The height of the rectangle. */
public final float height;
/**
* Constructs a rectangle that actually holds a point, not a triangle.
* This is a special case that is sometimes used when generating a
* texture where UV coordinates are defined by normals instead of
* vertices.
*
* @param pointPosition
* a position in 3D space
*/
public RectangleEnvelope(Vector3f pointPosition) {
this.min = pointPosition;
this.h = this.w = Vector3f.ZERO;
this.width = this.height = 1;
}
/**
* Constructs a rectangle envelope.
*
* @param min
* the minimum rectangle point
* @param w
* the width vector
* @param h
* the height vector
*/
public RectangleEnvelope(Vector3f min, Vector3f w, Vector3f h) {
this.min = min;
this.h = h;
this.w = w;
this.width = w.length();
this.height = h.length();
}
@Override
public String toString() {
return "Envelope[min = " + min + ", w = " + w + ", h = " + h + "]";
}
}
@Override
public void setWrap(WrapAxis axis, WrapMode mode) {
}
@Override
public void setWrap(WrapMode mode) {
}
@Override
public WrapMode getWrap(WrapAxis axis) {
return null;
}
@Override
public Type getType() {
return Type.TwoDimensional;
}
@Override
public Texture createSimpleClone() {
return null;
}
}

@ -31,6 +31,11 @@
*/ */
package com.jme3.scene.plugins.blender.textures; package com.jme3.scene.plugins.blender.textures;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere; import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume; import com.jme3.bounding.BoundingVolume;
@ -39,69 +44,68 @@ import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.util.List;
import java.util.logging.Logger;
/** /**
* This class is used for UV coordinates generation. * This class is used for UV coordinates generation.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class UVCoordinatesGenerator { public class UVCoordinatesGenerator {
private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName()); private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName());
// texture UV coordinates types public static enum UVCoordinatesType {
public static final int TEXCO_ORCO = 1; TEXCO_ORCO(1),
public static final int TEXCO_REFL = 2; TEXCO_REFL(2),
public static final int TEXCO_NORM = 4; TEXCO_NORM(4),
public static final int TEXCO_GLOB = 8; TEXCO_GLOB(8),
public static final int TEXCO_UV = 16; TEXCO_UV(16),
public static final int TEXCO_OBJECT = 32; TEXCO_OBJECT(32),
public static final int TEXCO_LAVECTOR = 64; TEXCO_LAVECTOR(64),
public static final int TEXCO_VIEW = 128; TEXCO_VIEW(128),
public static final int TEXCO_STICKY = 256; TEXCO_STICKY(256),
public static final int TEXCO_OSA = 512; TEXCO_OSA(512),
public static final int TEXCO_WINDOW = 1024; TEXCO_WINDOW(1024),
public static final int NEED_UV = 2048; NEED_UV(2048),
public static final int TEXCO_TANGENT = 4096; TEXCO_TANGENT(4096),
// still stored in vertex->accum, 1 D // still stored in vertex->accum, 1 D
public static final int TEXCO_PARTICLE_OR_STRAND = 8192; // strand is used TEXCO_PARTICLE_OR_STRAND(8192),
public static final int TEXCO_STRESS = 16384; TEXCO_STRESS(16384),
public static final int TEXCO_SPEED = 32768; TEXCO_SPEED(32768);
public final int blenderValue;
// 2D texture mapping (projection) private UVCoordinatesType(int blenderValue) {
public static final int PROJECTION_FLAT = 0; this.blenderValue = blenderValue;
public static final int PROJECTION_CUBE = 1; }
public static final int PROJECTION_TUBE = 2;
public static final int PROJECTION_SPHERE = 3; public static UVCoordinatesType valueOf(int blenderValue) {
for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) {
if (coordinatesType.blenderValue == blenderValue) {
return coordinatesType;
}
}
return null;
}
}
/** /**
* This method generates UV coordinates for the given mesh. * Generates a UV coordinates for 2D texture.
* IMPORTANT! This method assumes that all geometries represent one node. *
* Each containing mesh with separate material. * @param mesh
* So all meshes have the same reference to vertex table which stores all their vertices. * the mesh we generate UV's for
* @param texco * @param texco
* texture coordinates type * UV coordinates type
* @param projection * @param projection
* the projection type for 2D textures * projection type
* @param textureDimension
* the dimension of the texture (only 2D and 3D)
* @param coordinatesSwappingIndexes
* an array that tells how UV-coordinates need to be swapped
* @param geometries * @param geometries
* a list of geometries the UV coordinates will be applied to * the geometris the given mesh belongs to (required to compute
* @return created UV-coordinates buffer * bounding box)
* @return UV coordinates for the given mesh
*/ */
public static VertexBuffer generateUVCoordinates(int texco, int projection, int textureDimension, int[] coordinatesSwappingIndexes, List<Geometry> geometries) { public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List<Geometry> geometries) {
if (textureDimension != 2 && textureDimension != 3) { List<Vector2f> result = new ArrayList<Vector2f>();
throw new IllegalStateException("Unsupported texture dimension: " + textureDimension);
}
VertexBuffer result = new VertexBuffer(VertexBuffer.Type.TexCoord);
Mesh mesh = geometries.get(0).getMesh();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc. float[] inputData = null;// positions, normals, reflection vectors, etc.
@ -109,18 +113,11 @@ public class UVCoordinatesGenerator {
case TEXCO_ORCO: case TEXCO_ORCO:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position));
break; break;
case TEXCO_UV: case TEXCO_UV:// this should be used if not defined by user explicitly
FloatBuffer uvCoordinatesBuffer = BufferUtils.createFloatBuffer(mesh.getVertexCount() * textureDimension);
Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) };
for (int i = 0; i < mesh.getVertexCount(); ++i) { for (int i = 0; i < mesh.getVertexCount(); ++i) {
Vector2f uv = data[i % 3]; result.add(data[i % 3]);
uvCoordinatesBuffer.put(uv.x);
uvCoordinatesBuffer.put(uv.y);
if(textureDimension == 3) {
uvCoordinatesBuffer.put(0);
}
} }
result.setupData(Usage.Static, textureDimension, Format.Float, uvCoordinatesBuffer);
break; break;
case TEXCO_NORM: case TEXCO_NORM:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal));
@ -143,63 +140,112 @@ public class UVCoordinatesGenerator {
throw new IllegalStateException("Unknown texture coordinates value: " + texco); throw new IllegalStateException("Unknown texture coordinates value: " + texco);
} }
if (inputData != null) {// make calculations if (inputData != null) {// make projection calculations
if (textureDimension == 2) {
switch (projection) { switch (projection) {
case PROJECTION_FLAT: case PROJECTION_FLAT:
inputData = UVProjectionGenerator.flatProjection(mesh, bb); inputData = UVProjectionGenerator.flatProjection(inputData, bb);
break; break;
case PROJECTION_CUBE: case PROJECTION_CUBE:
inputData = UVProjectionGenerator.cubeProjection(mesh, bb); inputData = UVProjectionGenerator.cubeProjection(inputData, bb);
break; break;
case PROJECTION_TUBE: case PROJECTION_TUBE:
BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries); BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries);
inputData = UVProjectionGenerator.tubeProjection(mesh, bt); inputData = UVProjectionGenerator.tubeProjection(inputData, bt);
break; break;
case PROJECTION_SPHERE: case PROJECTION_SPHERE:
BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries); BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries);
inputData = UVProjectionGenerator.sphereProjection(mesh, bs); inputData = UVProjectionGenerator.sphereProjection(inputData, bs);
break; break;
default: default:
throw new IllegalStateException("Unknown projection type: " + projection); throw new IllegalStateException("Unknown projection type: " + projection);
} }
} else { for (int i = 0; i < inputData.length; i += 2) {
result.add(new Vector2f(inputData[i], inputData[i + 1]));
}
}
return result;
}
/**
* Generates a UV coordinates for 3D texture.
*
* @param mesh
* the mesh we generate UV's for
* @param texco
* UV coordinates type
* @param coordinatesSwappingIndexes
* coordinates swapping indexes
* @param geometries
* the geometris the given mesh belongs to (required to compute
* bounding box)
* @return UV coordinates for the given mesh
*/
public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List<Geometry> geometries) {
List<Vector3f> result = new ArrayList<Vector3f>();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc.
switch (texco) {
case TEXCO_ORCO:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position));
break;
case TEXCO_UV:
Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) };
for (int i = 0; i < mesh.getVertexCount(); ++i) {
Vector2f uv = data[i % 3];
result.add(new Vector3f(uv.x, uv.y, 0));
}
break;
case TEXCO_NORM:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal));
break;
case TEXCO_REFL:
case TEXCO_GLOB:
case TEXCO_TANGENT:
case TEXCO_STRESS:
case TEXCO_LAVECTOR:
case TEXCO_OBJECT:
case TEXCO_OSA:
case TEXCO_PARTICLE_OR_STRAND:
case TEXCO_SPEED:
case TEXCO_STICKY:
case TEXCO_VIEW:
case TEXCO_WINDOW:
LOGGER.warning("Texture coordinates type not currently supported: " + texco);
break;
default:
throw new IllegalStateException("Unknown texture coordinates value: " + texco);
}
if (inputData != null) {// make calculations
Vector3f min = bb.getMin(null); Vector3f min = bb.getMin(null);
float[] uvCoordsResults = new float[4];// used for coordinates swapping float[] uvCoordsResults = new float[4];// used for coordinates swapping
float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 }; float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 };
for (int i = 0; i < ext.length; ++i) {
// now transform the coordinates so that they are in the range of <0; 1> if (ext[i] == 0) {
ext[i] = 1;
}
}
// now transform the coordinates so that they are in the range of
// <0; 1>
for (int i = 0; i < inputData.length; i += 3) { for (int i = 0; i < inputData.length; i += 3) {
uvCoordsResults[1] = (inputData[i] - min.x) / ext[0]; uvCoordsResults[1] = (inputData[i] - min.x) / ext[0];
uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1]; uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1];
uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2]; uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2];
result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]]));
inputData[i] = uvCoordsResults[coordinatesSwappingIndexes[0]];
inputData[i + 1] = uvCoordsResults[coordinatesSwappingIndexes[1]];
inputData[i + 2] = uvCoordsResults[coordinatesSwappingIndexes[2]];
}
} }
result.setupData(Usage.Static, textureDimension, Format.Float, BufferUtils.createFloatBuffer(inputData));
}
// each mesh will have the same coordinates
for (Geometry geometry : geometries) {
mesh = geometry.getMesh();
mesh.clearBuffer(VertexBuffer.Type.TexCoord);// in case there are coordinates already set
mesh.setBuffer(result);
} }
return result; return result;
} }
/** /**
* This method returns the bounding box of the given geometries. * This method returns the bounding box of the given geometries.
*
* @param geometries * @param geometries
* the list of geometries * the list of geometries
* @return bounding box of the given geometries * @return bounding box of the given geometries
*/ */
/* package */static BoundingBox getBoundingBox(List<Geometry> geometries) { public static BoundingBox getBoundingBox(List<Geometry> geometries) {
BoundingBox result = null; BoundingBox result = null;
for (Geometry geometry : geometries) { for (Geometry geometry : geometries) {
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh()); BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh());
@ -214,6 +260,7 @@ public class UVCoordinatesGenerator {
/** /**
* This method returns the bounding box of the given mesh. * This method returns the bounding box of the given mesh.
*
* @param mesh * @param mesh
* the mesh * the mesh
* @return bounding box of the given mesh * @return bounding box of the given mesh
@ -234,6 +281,7 @@ public class UVCoordinatesGenerator {
/** /**
* This method returns the bounding sphere of the given geometries. * This method returns the bounding sphere of the given geometries.
*
* @param geometries * @param geometries
* the list of geometries * the list of geometries
* @return bounding sphere of the given geometries * @return bounding sphere of the given geometries
@ -253,6 +301,7 @@ public class UVCoordinatesGenerator {
/** /**
* This method returns the bounding sphere of the given mesh. * This method returns the bounding sphere of the given mesh.
*
* @param mesh * @param mesh
* the mesh * the mesh
* @return bounding sphere of the given mesh * @return bounding sphere of the given mesh
@ -274,6 +323,7 @@ public class UVCoordinatesGenerator {
/** /**
* This method returns the bounding tube of the given mesh. * This method returns the bounding tube of the given mesh.
*
* @param mesh * @param mesh
* the mesh * the mesh
* @return bounding tube of the given mesh * @return bounding tube of the given mesh
@ -306,6 +356,7 @@ public class UVCoordinatesGenerator {
/** /**
* This method returns the bounding tube of the given geometries. * This method returns the bounding tube of the given geometries.
*
* @param geometries * @param geometries
* the list of geometries * the list of geometries
* @return bounding tube of the given geometries * @return bounding tube of the given geometries
@ -324,9 +375,11 @@ public class UVCoordinatesGenerator {
} }
/** /**
* A very simple bounding tube. Id holds only the basic data bout the bounding tube * A very simple bounding tube. Id holds only the basic data bout the
* and does not provide full functionality of a BoundingVolume. * bounding tube and does not provide full functionality of a
* Should be replaced with a bounding tube that extends the BoundingVolume if it is ever created. * BoundingVolume. Should be replaced with a bounding tube that extends the
* BoundingVolume if it is ever created.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */static class BoundingTube { /* package */static class BoundingTube {
@ -336,6 +389,7 @@ public class UVCoordinatesGenerator {
/** /**
* Constructor creates the tube with the given params. * Constructor creates the tube with the given params.
*
* @param radius * @param radius
* the radius of the tube * the radius of the tube
* @param height * @param height
@ -351,6 +405,7 @@ public class UVCoordinatesGenerator {
/** /**
* This method merges two bounding tubes. * This method merges two bounding tubes.
*
* @param boundingTube * @param boundingTube
* bounding tube to be merged woth the current one * bounding tube to be merged woth the current one
* @return new instance of bounding tube representing the tubes' merge * @return new instance of bounding tube representing the tubes' merge
@ -375,14 +430,14 @@ public class UVCoordinatesGenerator {
Vector3f center = tube1.center.add(distance.mult(0.5f)); Vector3f center = tube1.center.add(distance.mult(0.5f));
distance.z = 0;// projecting this vector on XY plane distance.z = 0;// projecting this vector on XY plane
float d = distance.length(); float d = distance.length();
// d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the inside // d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the
// inside
// d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1 // d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1
float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f; float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f;
return new BoundingTube(radius, height, center); return new BoundingTube(radius, height, center);
} }
/** /**
* This method returns the radius of the tube.
* @return the radius of the tube * @return the radius of the tube
*/ */
public float getRadius() { public float getRadius() {
@ -390,7 +445,6 @@ public class UVCoordinatesGenerator {
} }
/** /**
* This method returns the height of the tube.
* @return the height of the tube * @return the height of the tube
*/ */
public float getHeight() { public float getHeight() {
@ -398,7 +452,6 @@ public class UVCoordinatesGenerator {
} }
/** /**
* This method returns the center of the tube.
* @return the center of the tube * @return the center of the tube
*/ */
public Vector3f getCenter() { public Vector3f getCenter() {

@ -5,10 +5,7 @@ import com.jme3.bounding.BoundingSphere;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.math.Triangle; import com.jme3.math.Triangle;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.BoundingTube; import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.BoundingTube;
import java.nio.FloatBuffer;
/** /**
* This class helps with projection calculations. * This class helps with projection calculations.
@ -16,6 +13,32 @@ import java.nio.FloatBuffer;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class UVProjectionGenerator { /* package */class UVProjectionGenerator {
/**
* 2D texture mapping (projection)
* @author Marcin Roguski (Kaelthas)
*/
public static enum UVProjectionType {
PROJECTION_FLAT(0),
PROJECTION_CUBE(1),
PROJECTION_TUBE(2),
PROJECTION_SPHERE(3);
public final int blenderValue;
private UVProjectionType(int blenderValue) {
this.blenderValue = blenderValue;
}
public static UVProjectionType valueOf(int blenderValue) {
for(UVProjectionType projectionType : UVProjectionType.values()) {
if(projectionType.blenderValue == blenderValue) {
return projectionType;
}
}
return null;
}
}
/** /**
* Flat projection for 2D textures. * Flat projection for 2D textures.
* *
@ -25,18 +48,14 @@ import java.nio.FloatBuffer;
* the bounding box for projecting * the bounding box for projecting
* @return UV coordinates after the projection * @return UV coordinates after the projection
*/ */
public static float[] flatProjection(Mesh mesh, BoundingBox bb) { public static float[] flatProjection(float[] positions, BoundingBox bb) {
if (bb == null) {
bb = UVCoordinatesGenerator.getBoundingBox(mesh);
}
Vector3f min = bb.getMin(null); Vector3f min = bb.getMin(null);
float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f }; float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getZExtent() * 2.0f };
FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position); float[] uvCoordinates = new float[positions.length / 3 * 2];
float[] uvCoordinates = new float[positions.limit() / 3 * 2]; for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
for (int i = 0, j = 0; i < positions.limit(); i += 3, j += 2) { uvCoordinates[j] = (positions[i] - min.x) / ext[0];
uvCoordinates[j] = (positions.get(i) - min.x) / ext[0]; // skip the Y-coordinate
uvCoordinates[j + 1] = (positions.get(i + 1) - min.y) / ext[1]; uvCoordinates[j + 1] = (positions[i + 2] - min.z) / ext[1];
// skip the Z-coordinate
} }
return uvCoordinates; return uvCoordinates;
} }
@ -44,13 +63,13 @@ import java.nio.FloatBuffer;
/** /**
* Cube projection for 2D textures. * Cube projection for 2D textures.
* *
* @param mesh * @param positions
* mesh that is to be projected * points to be projected
* @param bb * @param bb
* the bounding box for projecting * the bounding box for projecting
* @return UV coordinates after the projection * @return UV coordinates after the projection
*/ */
public static float[] cubeProjection(Mesh mesh, BoundingBox bb) { public static float[] cubeProjection(float[] positions, BoundingBox bb) {
Triangle triangle = new Triangle(); Triangle triangle = new Triangle();
Vector3f x = new Vector3f(1, 0, 0); Vector3f x = new Vector3f(1, 0, 0);
Vector3f y = new Vector3f(0, 1, 0); Vector3f y = new Vector3f(0, 1, 0);
@ -58,10 +77,12 @@ import java.nio.FloatBuffer;
Vector3f min = bb.getMin(null); Vector3f min = bb.getMin(null);
float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f }; float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f };
float[] uvCoordinates = new float[mesh.getTriangleCount() * 6];// 6 == 3 * 2 float[] uvCoordinates = new float[positions.length/3*2];
float borderAngle = (float) Math.sqrt(2.0f) / 2.0f; float borderAngle = (float) Math.sqrt(2.0f) / 2.0f;
for (int i = 0, pointIndex = 0; i < mesh.getTriangleCount(); ++i) { for (int i = 0, pointIndex = 0; i < positions.length; i+=9) {
mesh.getTriangle(i, triangle); triangle.set(0, positions[i], positions[i + 1], positions[i + 2]);
triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
Vector3f n = triangle.getNormal(); Vector3f n = triangle.getNormal();
float dotNX = Math.abs(n.dot(x)); float dotNX = Math.abs(n.dot(x));
float dorNY = Math.abs(n.dot(y)); float dorNY = Math.abs(n.dot(y));
@ -107,23 +128,22 @@ import java.nio.FloatBuffer;
/** /**
* Tube projection for 2D textures. * Tube projection for 2D textures.
* *
* @param mesh * @param positions
* mesh that is to be projected * points to be projected
* @param bt * @param bt
* the bounding tube for projecting * the bounding tube for projecting
* @return UV coordinates after the projection * @return UV coordinates after the projection
*/ */
public static float[] tubeProjection(Mesh mesh, BoundingTube bt) { public static float[] tubeProjection(float[] positions, BoundingTube bt) {
FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position); float[] uvCoordinates = new float[positions.length / 3 * 2];
float[] uvCoordinates = new float[positions.limit() / 3 * 2];
Vector3f v = new Vector3f(); Vector3f v = new Vector3f();
float cx = bt.getCenter().x, cy = bt.getCenter().y; float cx = bt.getCenter().x, cz = bt.getCenter().z;
Vector3f uBase = new Vector3f(0, -1, 0); Vector3f uBase = new Vector3f(0, 0, -1);
float vBase = bt.getCenter().z - bt.getHeight() * 0.5f; float vBase = bt.getCenter().y - bt.getHeight() * 0.5f;
for (int i = 0, j = 0; i < positions.limit(); i += 3, j += 2) { for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
// calculating U // calculating U
v.set(positions.get(i)-cx, positions.get(i + 1)-cy, 0); v.set(positions[i]-cx, 0, positions[i + 2]-cz);
v.normalizeLocal(); v.normalizeLocal();
float angle = v.angleBetween(uBase);// result between [0; PI] float angle = v.angleBetween(uBase);// result between [0; PI]
if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then
@ -132,31 +152,32 @@ import java.nio.FloatBuffer;
uvCoordinates[j] = angle / FastMath.TWO_PI; uvCoordinates[j] = angle / FastMath.TWO_PI;
// calculating V // calculating V
float z = positions.get(i + 2); float y = positions[i + 1];
uvCoordinates[j + 1] = (z - vBase) / bt.getHeight(); uvCoordinates[j + 1] = (y - vBase) / bt.getHeight();
} }
//looking for splitted triangles //looking for splitted triangles
Triangle triangle = new Triangle(); Triangle triangle = new Triangle();
for(int i=0;i<mesh.getTriangleCount();++i) { for (int i = 0; i < positions.length; i+=9) {
mesh.getTriangle(i, triangle); triangle.set(0, positions[i], positions[i + 1], positions[i + 2]);
triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
float sgn1 = Math.signum(triangle.get1().x-cx); float sgn1 = Math.signum(triangle.get1().x-cx);
float sgn2 = Math.signum(triangle.get2().x-cx); float sgn2 = Math.signum(triangle.get2().x-cx);
float sgn3 = Math.signum(triangle.get3().x-cx); float sgn3 = Math.signum(triangle.get3().x-cx);
float xSideFactor = sgn1 + sgn2 + sgn3; float xSideFactor = sgn1 + sgn2 + sgn3;
float ySideFactor = Math.signum(triangle.get1().y-cy)+ float ySideFactor = Math.signum(triangle.get1().z-cz)+
Math.signum(triangle.get2().y-cy)+ Math.signum(triangle.get2().z-cz)+
Math.signum(triangle.get3().y-cy); Math.signum(triangle.get3().z-cz);
if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane
//indexOfUcoord = (indexOfTriangle*3 + indexOfTrianglesVertex)*2
if(sgn1==1.0f) { if(sgn1==1.0f) {
uvCoordinates[i*3*2] += 1.0f; uvCoordinates[i/3*2] += 1.0f;
} }
if(sgn2==1.0f) { if(sgn2==1.0f) {
uvCoordinates[(i*3+1)*2] += 1.0f; uvCoordinates[(i/3+1)*2] += 1.0f;
} }
if(sgn3==1.0f) { if(sgn3==1.0f) {
uvCoordinates[(i*3+2)*2] += 1.0f; uvCoordinates[(i/3+2)*2] += 1.0f;
} }
} }
} }
@ -166,23 +187,22 @@ import java.nio.FloatBuffer;
/** /**
* Sphere projection for 2D textures. * Sphere projection for 2D textures.
* *
* @param mesh * @param positions
* mesh that is to be projected * points to be projected
* @param bb * @param bb
* the bounding box for projecting * the bounding box for projecting
* @return UV coordinates after the projection * @return UV coordinates after the projection
*/ */
public static float[] sphereProjection(Mesh mesh, BoundingSphere bs) { public static float[] sphereProjection(float[] positions, BoundingSphere bs) {//TODO: rotate it to be vertical
FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position); float[] uvCoordinates = new float[positions.length / 3 * 2];
float[] uvCoordinates = new float[positions.limit() / 3 * 2];
Vector3f v = new Vector3f(); Vector3f v = new Vector3f();
float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z; float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z;
Vector3f uBase = new Vector3f(0, -1, 0); Vector3f uBase = new Vector3f(0, -1, 0);
Vector3f vBase = new Vector3f(0, 0, -1); Vector3f vBase = new Vector3f(0, 0, -1);
for (int i = 0, j = 0; i < positions.limit(); i += 3, j += 2) { for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
// calculating U // calculating U
v.set(positions.get(i)-cx, positions.get(i + 1)-cy, 0); v.set(positions[i]-cx, positions[i + 1] - cy, 0);
v.normalizeLocal(); v.normalizeLocal();
float angle = v.angleBetween(uBase);// result between [0; PI] float angle = v.angleBetween(uBase);// result between [0; PI]
if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then
@ -191,7 +211,7 @@ import java.nio.FloatBuffer;
uvCoordinates[j] = angle / FastMath.TWO_PI; uvCoordinates[j] = angle / FastMath.TWO_PI;
// calculating V // calculating V
v.set(positions.get(i)-cx, positions.get(i + 1)-cy, positions.get(i + 2)-cz); v.set(positions[i]-cx, positions[i + 1]-cy, positions[i + 2]-cz);
v.normalizeLocal(); v.normalizeLocal();
angle = v.angleBetween(vBase);// result between [0; PI] angle = v.angleBetween(vBase);// result between [0; PI]
uvCoordinates[j+1] = angle / FastMath.PI; uvCoordinates[j+1] = angle / FastMath.PI;
@ -199,8 +219,10 @@ import java.nio.FloatBuffer;
//looking for splitted triangles //looking for splitted triangles
Triangle triangle = new Triangle(); Triangle triangle = new Triangle();
for(int i=0;i<mesh.getTriangleCount();++i) { for (int i = 0; i < positions.length; i+=9) {
mesh.getTriangle(i, triangle); triangle.set(0, positions[i], positions[i + 1], positions[i + 2]);
triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
float sgn1 = Math.signum(triangle.get1().x-cx); float sgn1 = Math.signum(triangle.get1().x-cx);
float sgn2 = Math.signum(triangle.get2().x-cx); float sgn2 = Math.signum(triangle.get2().x-cx);
float sgn3 = Math.signum(triangle.get3().x-cx); float sgn3 = Math.signum(triangle.get3().x-cx);
@ -209,15 +231,14 @@ import java.nio.FloatBuffer;
Math.signum(triangle.get2().y-cy)+ Math.signum(triangle.get2().y-cy)+
Math.signum(triangle.get3().y-cy); Math.signum(triangle.get3().y-cy);
if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane
//indexOfUcoord = (indexOfTriangle*3 + indexOfTrianglesVertex)*2
if(sgn1==1.0f) { if(sgn1==1.0f) {
uvCoordinates[i*3*2] += 1.0f; uvCoordinates[i/3*2] += 1.0f;
} }
if(sgn2==1.0f) { if(sgn2==1.0f) {
uvCoordinates[(i*3+1)*2] += 1.0f; uvCoordinates[(i/3+1)*2] += 1.0f;
} }
if(sgn3==1.0f) { if(sgn3==1.0f) {
uvCoordinates[(i*3+2)*2] += 1.0f; uvCoordinates[(i/3+2)*2] += 1.0f;
} }
} }
} }

@ -1,6 +1,7 @@
package com.jme3.scene.plugins.blender.textures.blending; package com.jme3.scene.plugins.blender.textures.blending;
import com.jme3.math.FastMath; import java.util.logging.Logger;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.scene.plugins.blender.materials.MaterialHelper;
@ -11,113 +12,22 @@ import com.jme3.scene.plugins.blender.materials.MaterialHelper;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */abstract class AbstractTextureBlender implements TextureBlender { /* package */abstract class AbstractTextureBlender implements TextureBlender {
/** private static final Logger LOGGER = Logger.getLogger(AbstractTextureBlender.class.getName());
* This method blends the single pixel depending on the blending type.
*
* @param result
* the result pixel
* @param materialColor
* the material color
* @param pixelColor
* the pixel color
* @param blendFactor
* the blending factor
* @param blendtype
* the blending type
* @param blenderContext
* the blender context
*/
protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, float blendFactor, int blendtype, BlenderContext blenderContext) {
float oneMinusFactor = 1.0f - blendFactor, col;
switch (blendtype) { protected int flag;
case MTEX_BLEND: protected boolean negateTexture;
result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0]; protected int blendType;
result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1]; protected float[] materialColor;
result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2]; protected float[] color;
break; protected float blendFactor;
case MTEX_MUL:
result[0] = (oneMinusFactor + blendFactor * materialColor[0]) * pixelColor[0]; public AbstractTextureBlender(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
result[1] = (oneMinusFactor + blendFactor * materialColor[1]) * pixelColor[1]; this.flag = flag;
result[2] = (oneMinusFactor + blendFactor * materialColor[2]) * pixelColor[2]; this.negateTexture = negateTexture;
break; this.blendType = blendType;
case MTEX_DIV: this.materialColor = materialColor;
if (pixelColor[0] != 0.0) { this.color = color;
result[0] = (oneMinusFactor * materialColor[0] + blendFactor * materialColor[0] / pixelColor[0]) * 0.5f; this.blendFactor = blendFactor;
}
if (pixelColor[1] != 0.0) {
result[1] = (oneMinusFactor * materialColor[1] + blendFactor * materialColor[1] / pixelColor[1]) * 0.5f;
}
if (pixelColor[2] != 0.0) {
result[2] = (oneMinusFactor * materialColor[2] + blendFactor * materialColor[2] / pixelColor[2]) * 0.5f;
}
break;
case MTEX_SCREEN:
result[0] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]);
result[1] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]);
result[2] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);
break;
case MTEX_OVERLAY:
if (materialColor[0] < 0.5f) {
result[0] = pixelColor[0] * (oneMinusFactor + 2.0f * blendFactor * materialColor[0]);
} else {
result[0] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]);
}
if (materialColor[1] < 0.5f) {
result[1] = pixelColor[1] * (oneMinusFactor + 2.0f * blendFactor * materialColor[1]);
} else {
result[1] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]);
}
if (materialColor[2] < 0.5f) {
result[2] = pixelColor[2] * (oneMinusFactor + 2.0f * blendFactor * materialColor[2]);
} else {
result[2] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);
}
break;
case MTEX_SUB:
result[0] = materialColor[0] - blendFactor * pixelColor[0];
result[1] = materialColor[1] - blendFactor * pixelColor[1];
result[2] = materialColor[2] - blendFactor * pixelColor[2];
result[0] = FastMath.clamp(result[0], 0.0f, 1.0f);
result[1] = FastMath.clamp(result[1], 0.0f, 1.0f);
result[2] = FastMath.clamp(result[2], 0.0f, 1.0f);
break;
case MTEX_ADD:
result[0] = (blendFactor * pixelColor[0] + materialColor[0]) * 0.5f;
result[1] = (blendFactor * pixelColor[1] + materialColor[1]) * 0.5f;
result[2] = (blendFactor * pixelColor[2] + materialColor[2]) * 0.5f;
break;
case MTEX_DIFF:
result[0] = oneMinusFactor * materialColor[0] + blendFactor * Math.abs(materialColor[0] - pixelColor[0]);
result[1] = oneMinusFactor * materialColor[1] + blendFactor * Math.abs(materialColor[1] - pixelColor[1]);
result[2] = oneMinusFactor * materialColor[2] + blendFactor * Math.abs(materialColor[2] - pixelColor[2]);
break;
case MTEX_DARK:
col = blendFactor * pixelColor[0];
result[0] = col < materialColor[0] ? col : materialColor[0];
col = blendFactor * pixelColor[1];
result[1] = col < materialColor[1] ? col : materialColor[1];
col = blendFactor * pixelColor[2];
result[2] = col < materialColor[2] ? col : materialColor[2];
break;
case MTEX_LIGHT:
col = blendFactor * pixelColor[0];
result[0] = col > materialColor[0] ? col : materialColor[0];
col = blendFactor * pixelColor[1];
result[1] = col > materialColor[1] ? col : materialColor[1];
col = blendFactor * pixelColor[2];
result[2] = col > materialColor[2] ? col : materialColor[2];
break;
case MTEX_BLEND_HUE:
case MTEX_BLEND_SAT:
case MTEX_BLEND_VAL:
case MTEX_BLEND_COLOR:
System.arraycopy(materialColor, 0, result, 0, 3);
this.blendHSV(blendtype, result, blendFactor, pixelColor, blenderContext);
break;
default:
throw new IllegalStateException("Unknown blend type: " + blendtype);
}
} }
/** /**
@ -139,8 +49,7 @@ import com.jme3.scene.plugins.blender.materials.MaterialHelper;
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
switch (type) { switch (type) {
case MTEX_BLEND_HUE: {// FIXME: not working well for image textures case MTEX_BLEND_HUE: {// FIXME: not working well for image textures (works fine for generated textures)
// (works fine for generated textures)
float[] colorTransformResult = new float[3]; float[] colorTransformResult = new float[3];
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);
if (colorTransformResult[0] != 0.0f) { if (colorTransformResult[0] != 0.0f) {
@ -161,7 +70,7 @@ import com.jme3.scene.plugins.blender.materials.MaterialHelper;
float v = colorTransformResult[2]; float v = colorTransformResult[2];
if (s != 0.0f) { if (s != 0.0f) {
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);
materialHelper.hsvToRgb(h, (oneMinusFactor * s + fac * colorTransformResult[1]), v, materialRGB); materialHelper.hsvToRgb(h, oneMinusFactor * s + fac * colorTransformResult[1], v, materialRGB);
} }
break; break;
} }
@ -170,12 +79,10 @@ import com.jme3.scene.plugins.blender.materials.MaterialHelper;
float[] colToHsv = new float[3]; float[] colToHsv = new float[3];
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv);
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv);
materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], (oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2]), materialRGB); materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2], materialRGB);
break; break;
} }
case MTEX_BLEND_COLOR: {// FIXME: not working well for image case MTEX_BLEND_COLOR: {// FIXME: not working well for image textures (works fine for generated textures)
// textures (works fine for generated
// textures)
float[] rgbToHsv = new float[3]; float[] rgbToHsv = new float[3];
float[] colToHsv = new float[3]; float[] colToHsv = new float[3];
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv);
@ -192,4 +99,18 @@ import com.jme3.scene.plugins.blender.materials.MaterialHelper;
throw new IllegalStateException("Unknown ramp type: " + type); throw new IllegalStateException("Unknown ramp type: " + type);
} }
} }
@Override
public void copyBlendingData(TextureBlender textureBlender) {
if(textureBlender instanceof AbstractTextureBlender) {
this.flag = ((AbstractTextureBlender) textureBlender).flag;
this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture;
this.blendType = ((AbstractTextureBlender) textureBlender).blendType;
this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone();
this.color = ((AbstractTextureBlender) textureBlender).color.clone();
this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor;
} else {
LOGGER.warning("Cannot copy blending data from other types than " + this.getClass());
}
}
} }

@ -1,7 +1,7 @@
package com.jme3.scene.plugins.blender.textures.blending; package com.jme3.scene.plugins.blender.textures.blending;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.texture.Texture; import com.jme3.texture.Image;
/** /**
* An interface for texture blending classes (the classes that mix the texture * An interface for texture blending classes (the classes that mix the texture
@ -32,20 +32,22 @@ public interface TextureBlender {
* color in 'map to' panel. As a result of this method a new texture is * color in 'map to' panel. As a result of this method a new texture is
* created. The input texture is NOT. * created. The input texture is NOT.
* *
* @param materialColor * @param image
* the material diffuse color * the image we use in blending
* @param texture * @param baseImage
* the texture we use in blending * the texture that is underneath the current texture (its pixels
* @param color * will be used instead of material color)
* the color defined for the texture
* @param affectFactor
* the factor that the color affects the texture (value form 0.0
* to 1.0)
* @param blendType
* the blending type
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return new texture that was created after the blending * @return new image that was created after the blending
*/ */
Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext); Image blend(Image image, Image baseImage, BlenderContext blenderContext);
/**
* Copies blending data. Used for blending type format changing.
*
* @param textureBlender
* the blend data that should be copied
*/
void copyBlendingData(TextureBlender textureBlender);
} }

@ -2,14 +2,13 @@ package com.jme3.scene.plugins.blender.textures.blending;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
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;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.Texture3D;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
@ -36,127 +35,178 @@ import com.jme3.util.BufferUtils;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureBlenderAWT extends AbstractTextureBlender { public class TextureBlenderAWT extends AbstractTextureBlender {
private static final Logger LOGGER = Logger.getLogger(TextureBlenderAWT.class.getName()); public TextureBlenderAWT(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
super(flag, negateTexture, blendType, materialColor, color, blendFactor);
}
@Override @Override
public Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext) { public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f }; float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f };
Format format = texture.getImage().getFormat(); Format format = image.getFormat();
ByteBuffer data = texture.getImage().getData(0); ByteBuffer data = image.getData(0);
data.rewind();
PixelInputOutput basePixelIO = null, pixelReader = PixelIOFactory.getPixelIO(format);
TexturePixel basePixel = null, pixel = new TexturePixel();
float[] materialColor = this.materialColor;
if(baseImage != null) {
basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat());
materialColor = new float[this.materialColor.length];
basePixel = new TexturePixel();
}
int width = texture.getImage().getWidth(); int width = image.getWidth();
int height = texture.getImage().getHeight(); int height = image.getHeight();
int depth = texture.getImage().getDepth(); int depth = image.getDepth();
if (depth == 0) { if (depth == 0) {
depth = 1; depth = 1;
} }
ByteBuffer newData = BufferUtils.createByteBuffer(width * height * depth * 4); ByteBuffer newData = BufferUtils.createByteBuffer(width * height * depth * 4);
float[] resultPixel = new float[4]; float[] resultPixel = new float[4];
int dataIndex = 0; int dataIndex = 0, x = 0, y = 0, index = 0;
while (data.hasRemaining()) { while (index < data.limit()) {
this.setupMaterialColor(data, format, neg, pixelColor); //getting the proper material color if the base texture is applied
this.blendPixel(resultPixel, materialColor, pixelColor, affectFactor, blendType, blenderContext); if(basePixelIO != null) {
basePixelIO.read(baseImage, basePixel, x, y);
basePixel.toRGBA(materialColor);
}
//reading the current texture's pixel
pixelReader.read(image, pixel, index);
index += image.getFormat().getBitsPerPixel() >> 3;
pixel.toRGBA(pixelColor);
if (negateTexture) {
pixel.negate();
}
this.blendPixel(resultPixel, materialColor, pixelColor, blenderContext);
newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));
newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));
newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));
newData.put(dataIndex++, (byte) (pixelColor[3] * 255.0f)); newData.put(dataIndex++, (byte) (pixelColor[3] * 255.0f));
++x;
if(x >= width) {
x = 0;
++y;
} }
if (texture.getType() == Texture.Type.TwoDimensional) { }
return new Texture2D(new Image(Format.RGBA8, width, height, newData)); if(depth > 1) {
} else {
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1); ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(newData); dataArray.add(newData);
return new Texture3D(new Image(Format.RGBA8, width, height, depth, dataArray)); return new Image(Format.RGBA8, width, height, depth, dataArray);
} else {
return new Image(Format.RGBA8, width, height, newData);
} }
} }
/** /**
* This method alters the material color in a way dependent on the type of * This method blends the single pixel depending on the blending type.
* the image. For example the color remains untouched if the texture is of
* Luminance type. The luminance defines the interaction between the
* material color and color defined for texture blending. If the type has 3
* or more color channels then the material color is replaced with the
* texture's color and later blended with the defined blend color. All alpha
* values (if present) are ignored and not used during blending.
* *
* @param data * @param result
* the image data * the result pixel
* @param imageFormat
* the format of the image
* @param neg
* defines it the result color should be nagated
* @param materialColor * @param materialColor
* the material's color (value may be changed) * the material color
* @return texture intensity for the current pixel * @param pixelColor
* the pixel color
* @param blendFactor
* the blending factor
* @param blendtype
* the blending type
* @param blenderContext
* the blender context
*/ */
protected float setupMaterialColor(ByteBuffer data, Format imageFormat, boolean neg, float[] materialColor) { protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, BlenderContext blenderContext) {
float tin = 0.0f; float blendFactor = this.blendFactor * pixelColor[3];
byte pixelValue = data.get();// at least one byte is always taken :) float oneMinusFactor = 1.0f - blendFactor, col;
float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
switch (imageFormat) { switch (blendType) {
case RGBA8: case MTEX_BLEND:
materialColor[0] = firstPixelValue; result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0];
pixelValue = data.get(); result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1];
materialColor[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2];
pixelValue = data.get(); break;
materialColor[2] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; case MTEX_MUL:
pixelValue = data.get(); result[0] = (oneMinusFactor + blendFactor * materialColor[0]) * pixelColor[0];
materialColor[3] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; result[1] = (oneMinusFactor + blendFactor * materialColor[1]) * pixelColor[1];
result[2] = (oneMinusFactor + blendFactor * materialColor[2]) * pixelColor[2];
break;
case MTEX_DIV:
if (pixelColor[0] != 0.0) {
result[0] = (oneMinusFactor * materialColor[0] + blendFactor * materialColor[0] / pixelColor[0]) * 0.5f;
}
if (pixelColor[1] != 0.0) {
result[1] = (oneMinusFactor * materialColor[1] + blendFactor * materialColor[1] / pixelColor[1]) * 0.5f;
}
if (pixelColor[2] != 0.0) {
result[2] = (oneMinusFactor * materialColor[2] + blendFactor * materialColor[2] / pixelColor[2]) * 0.5f;
}
break;
case MTEX_SCREEN:
result[0] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]);
result[1] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]);
result[2] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);
break;
case MTEX_OVERLAY:
if (materialColor[0] < 0.5f) {
result[0] = pixelColor[0] * (oneMinusFactor + 2.0f * blendFactor * materialColor[0]);
} else {
result[0] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]);
}
if (materialColor[1] < 0.5f) {
result[1] = pixelColor[1] * (oneMinusFactor + 2.0f * blendFactor * materialColor[1]);
} else {
result[1] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]);
}
if (materialColor[2] < 0.5f) {
result[2] = pixelColor[2] * (oneMinusFactor + 2.0f * blendFactor * materialColor[2]);
} else {
result[2] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);
}
break; break;
case ABGR8: case MTEX_SUB:
materialColor[3] = firstPixelValue; result[0] = materialColor[0] - blendFactor * pixelColor[0];
pixelValue = data.get(); result[1] = materialColor[1] - blendFactor * pixelColor[1];
materialColor[2] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; result[2] = materialColor[2] - blendFactor * pixelColor[2];
pixelValue = data.get(); result[0] = FastMath.clamp(result[0], 0.0f, 1.0f);
materialColor[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; result[1] = FastMath.clamp(result[1], 0.0f, 1.0f);
pixelValue = data.get(); result[2] = FastMath.clamp(result[2], 0.0f, 1.0f);
materialColor[0] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
break; break;
case BGR8: case MTEX_ADD:
materialColor[2] = firstPixelValue; result[0] = (blendFactor * pixelColor[0] + materialColor[0]) * 0.5f;
pixelValue = data.get(); result[1] = (blendFactor * pixelColor[1] + materialColor[1]) * 0.5f;
materialColor[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; result[2] = (blendFactor * pixelColor[2] + materialColor[2]) * 0.5f;
pixelValue = data.get();
materialColor[0] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
materialColor[3] = 1.0f;
break; break;
case RGB8: case MTEX_DIFF:
materialColor[0] = firstPixelValue; result[0] = oneMinusFactor * materialColor[0] + blendFactor * Math.abs(materialColor[0] - pixelColor[0]);
pixelValue = data.get(); result[1] = oneMinusFactor * materialColor[1] + blendFactor * Math.abs(materialColor[1] - pixelColor[1]);
materialColor[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; result[2] = oneMinusFactor * materialColor[2] + blendFactor * Math.abs(materialColor[2] - pixelColor[2]);
pixelValue = data.get();
materialColor[2] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;
materialColor[3] = 1.0f;
break; break;
case ARGB4444: case MTEX_DARK:
case RGB10: col = blendFactor * pixelColor[0];
case RGB111110F: result[0] = col < materialColor[0] ? col : materialColor[0];
case RGB16: col = blendFactor * pixelColor[1];
case RGB16F: result[1] = col < materialColor[1] ? col : materialColor[1];
case RGB16F_to_RGB111110F: col = blendFactor * pixelColor[2];
case RGB16F_to_RGB9E5: result[2] = col < materialColor[2] ? col : materialColor[2];
case RGB32F: break;
case RGB565: case MTEX_LIGHT:
case RGB5A1: col = blendFactor * pixelColor[0];
case RGB9E5: result[0] = col > materialColor[0] ? col : materialColor[0];
case RGBA16: col = blendFactor * pixelColor[1];
case RGBA16F: result[1] = col > materialColor[1] ? col : materialColor[1];
case RGBA32F:// TODO: implement these textures col = blendFactor * pixelColor[2];
LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat); result[2] = col > materialColor[2] ? col : materialColor[2];
break;
case MTEX_BLEND_HUE:
case MTEX_BLEND_SAT:
case MTEX_BLEND_VAL:
case MTEX_BLEND_COLOR:
System.arraycopy(materialColor, 0, result, 0, 3);
this.blendHSV(blendType, result, blendFactor, pixelColor, blenderContext);
break; break;
default: default:
throw new IllegalStateException("Invalid image format type for AWT texture blender: " + imageFormat); throw new IllegalStateException("Unknown blend type: " + blendType);
}
if (neg) {
materialColor[0] = 1.0f - materialColor[0];
materialColor[1] = 1.0f - materialColor[1];
materialColor[2] = 1.0f - materialColor[2];
} }
// Blender formula for texture intensity calculation:
// 0.35*texres.tr+0.45*texres.tg+0.2*texres.tb
tin = 0.35f * materialColor[0] + 0.45f * materialColor[1] + 0.2f * materialColor[2];
return tin;
} }
} }

@ -11,9 +11,6 @@ import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.textures.TexturePixel; import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image; import com.jme3.texture.Image;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
/** /**
@ -25,18 +22,23 @@ import com.jme3.util.BufferUtils;
* <li> DXT1A: * <li> DXT1A:
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureBlenderDDS extends AbstractTextureBlender { public class TextureBlenderDDS extends TextureBlenderAWT {
private static final Logger LOGGER = Logger.getLogger(TextureBlenderDDS.class.getName()); private static final Logger LOGGER = Logger.getLogger(TextureBlenderDDS.class.getName());
public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
super(flag, negateTexture, blendType, materialColor, color, blendFactor);
}
//TODO: implement using base texture
@Override @Override
public Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext) { public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
Format format = texture.getImage().getFormat(); Format format = image.getFormat();
ByteBuffer data = texture.getImage().getData(0); ByteBuffer data = image.getData(0);
data.rewind(); data.rewind();
int width = texture.getImage().getWidth(); int width = image.getWidth();
int height = texture.getImage().getHeight(); int height = image.getHeight();
int depth = texture.getImage().getDepth(); int depth = image.getDepth();
if (depth == 0) { if (depth == 0) {
depth = 1; depth = 1;
} }
@ -50,9 +52,7 @@ public class TextureBlenderDDS extends AbstractTextureBlender {
switch (format) { switch (format) {
case DXT3: case DXT3:
case DXT5: case DXT5:
newData.putLong(dataIndex, data.getLong());// just copy the newData.putLong(dataIndex, data.getLong());// just copy the 8 bytes of alphas
// 8 bytes of
// alphas
dataIndex += 8; dataIndex += 8;
case DXT1: case DXT1:
int col0 = RGB565.RGB565_to_ARGB8(data.getShort()); int col0 = RGB565.RGB565_to_ARGB8(data.getShort());
@ -69,11 +69,11 @@ public class TextureBlenderDDS extends AbstractTextureBlender {
// blending colors // blending colors
for (int i = 0; i < colors.length; ++i) { for (int i = 0; i < colors.length; ++i) {
if (neg) { if (negateTexture) {
colors[i].negate(); colors[i].negate();
} }
colors[i].toRGBA(pixelColor); colors[i].toRGBA(pixelColor);
this.blendPixel(resultPixel, materialColor, pixelColor, affectFactor, blendType, blenderContext); this.blendPixel(resultPixel, materialColor, pixelColor, blenderContext);
colors[i].fromARGB8(1, resultPixel[0], resultPixel[1], resultPixel[2]); colors[i].fromARGB8(1, resultPixel[0], resultPixel[1], resultPixel[2]);
int argb8 = colors[i].toARGB8(); int argb8 = colors[i].toARGB8();
short rgb565 = RGB565.ARGB8_to_RGB565(argb8); short rgb565 = RGB565.ARGB8_to_RGB565(argb8);
@ -85,12 +85,13 @@ public class TextureBlenderDDS extends AbstractTextureBlender {
newData.putInt(dataIndex, data.getInt()); newData.putInt(dataIndex, data.getInt());
dataIndex += 4; dataIndex += 4;
} }
if (texture.getType() == Texture.Type.TwoDimensional) {
return new Texture2D(new Image(format, width, height, newData)); if(depth > 1) {
} else {
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1); ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(newData); dataArray.add(newData);
return new Texture3D(new Image(format, width, height, depth, dataArray)); return new Image(format, width, height, depth, dataArray);
} else {
return new Image(format, width, height, newData);
} }
} }
} }

@ -35,7 +35,7 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.texture.Texture; import com.jme3.texture.Image;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
/** /**
@ -53,7 +53,7 @@ public class TextureBlenderFactory {
* the texture format * the texture format
* @return texture blending class * @return texture blending class
*/ */
public static TextureBlender createTextureBlender(Format format) { public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) {
switch (format) { switch (format) {
case Luminance8: case Luminance8:
case Luminance8Alpha8: case Luminance8Alpha8:
@ -62,7 +62,7 @@ public class TextureBlenderFactory {
case Luminance16F: case Luminance16F:
case Luminance16FAlpha16F: case Luminance16FAlpha16F:
case Luminance32F: case Luminance32F:
return new TextureBlenderLuminance(); return new TextureBlenderLuminance(flag, negate, blendType, materialColor, color, colfac);
case RGBA8: case RGBA8:
case ABGR8: case ABGR8:
case BGR8: case BGR8:
@ -80,12 +80,12 @@ public class TextureBlenderFactory {
case RGBA16: case RGBA16:
case RGBA16F: case RGBA16F:
case RGBA32F: case RGBA32F:
return new TextureBlenderAWT(); return new TextureBlenderAWT(flag, negate, blendType, materialColor, color, colfac);
case DXT1: case DXT1:
case DXT1A: case DXT1A:
case DXT3: case DXT3:
case DXT5: case DXT5:
return new TextureBlenderDDS(); return new TextureBlenderDDS(flag, negate, blendType, materialColor, color, colfac);
case Alpha16: case Alpha16:
case Alpha8: case Alpha8:
case ARGB4444: case ARGB4444:
@ -101,12 +101,31 @@ public class TextureBlenderFactory {
LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}. Returning a blender that does not change the texture.", format); LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}. Returning a blender that does not change the texture.", format);
return new TextureBlender() { return new TextureBlender() {
@Override @Override
public Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext) { public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
return texture; return image;
}
@Override
public void copyBlendingData(TextureBlender textureBlender) {
} }
}; };
default: default:
throw new IllegalStateException("Unknown image format type: " + format); throw new IllegalStateException("Unknown image format type: " + format);
} }
} }
/**
* This method changes the image format in the texture blender.
*
* @param format
* the new image format
* @param textureBlender
* the texture blender that will be altered
* @return altered texture blender
*/
public static TextureBlender alterTextureType(Format format, TextureBlender textureBlender) {
TextureBlender result = TextureBlenderFactory.createTextureBlender(format, 0, false, 0, null, null, 0);
result.copyBlendingData(textureBlender);
return result;
}
} }

@ -7,10 +7,10 @@ import java.util.logging.Logger;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
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;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.Texture3D;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
@ -29,15 +29,28 @@ import com.jme3.util.BufferUtils;
public class TextureBlenderLuminance extends AbstractTextureBlender { public class TextureBlenderLuminance extends AbstractTextureBlender {
private static final Logger LOGGER = Logger.getLogger(TextureBlenderLuminance.class.getName()); private static final Logger LOGGER = Logger.getLogger(TextureBlenderLuminance.class.getName());
public TextureBlenderLuminance(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
super(flag, negateTexture, blendType, materialColor, color, blendFactor);
}
@Override @Override
public Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext) { public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
Format format = texture.getImage().getFormat(); Format format = image.getFormat();
ByteBuffer data = texture.getImage().getData(0); ByteBuffer data = image.getData(0);
data.rewind(); data.rewind();
int width = texture.getImage().getWidth(); PixelInputOutput basePixelIO = null;
int height = texture.getImage().getHeight(); TexturePixel basePixel = null;
int depth = texture.getImage().getDepth(); float[] materialColor = this.materialColor;
if(baseImage != null) {
basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat());
materialColor = new float[this.materialColor.length];
basePixel = new TexturePixel();
}
int width = image.getWidth();
int height = image.getHeight();
int depth = image.getDepth();
if (depth == 0) { if (depth == 0) {
depth = 1; depth = 1;
} }
@ -45,21 +58,34 @@ public class TextureBlenderLuminance extends AbstractTextureBlender {
float[] resultPixel = new float[4]; float[] resultPixel = new float[4];
float[] tinAndAlpha = new float[2]; float[] tinAndAlpha = new float[2];
int dataIndex = 0; int dataIndex = 0, x = 0, y = 0;
while (data.hasRemaining()) { while (data.hasRemaining()) {
this.getTinAndAlpha(data, format, neg, tinAndAlpha); //getting the proper material color if the base texture is applied
this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], affectFactor, blendType, blenderContext); if(basePixelIO != null) {
basePixelIO.read(baseImage, basePixel, x, y);
basePixel.toRGBA(materialColor);
}
this.getTinAndAlpha(data, format, negateTexture, tinAndAlpha);
this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], blendFactor, blendType, blenderContext);
newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));
newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));
newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));
newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f)); newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f));
++x;
if(x >= width) {
x = 0;
++y;
} }
if (texture.getType() == Texture.Type.TwoDimensional) { }
return new Texture2D(new Image(Format.RGBA8, width, height, newData));
} else { if(depth > 1) {
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1); ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(newData); dataArray.add(newData);
return new Texture3D(new Image(Format.RGBA8, width, height, depth, dataArray)); return new Image(Format.RGBA8, width, height, depth, dataArray);
} else {
return new Image(Format.RGBA8, width, height, newData);
} }
} }
@ -77,7 +103,7 @@ public class TextureBlenderLuminance extends AbstractTextureBlender {
*/ */
protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) { protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) {
byte pixelValue = data.get();// at least one byte is always taken byte pixelValue = data.get();// at least one byte is always taken
float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
switch (imageFormat) { switch (imageFormat) {
case Luminance8: case Luminance8:
result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue;
@ -86,7 +112,7 @@ public class TextureBlenderLuminance extends AbstractTextureBlender {
case Luminance8Alpha8: case Luminance8Alpha8:
result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue;
pixelValue = data.get(); pixelValue = data.get();
result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f; result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
break; break;
case Luminance16: case Luminance16:
case Luminance16Alpha16: case Luminance16Alpha16:
@ -96,7 +122,7 @@ public class TextureBlenderLuminance extends AbstractTextureBlender {
LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat); LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat);
break; break;
default: default:
throw new IllegalStateException("Invalid image format type for DDS texture blender: " + imageFormat); throw new IllegalStateException("Invalid image format type for Luminance texture blender: " + imageFormat);
} }
} }

@ -29,13 +29,14 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.scene.plugins.blender.textures; package com.jme3.scene.plugins.blender.textures.generating;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.TextureGeneratorMusgrave.MusgraveData; import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorMusgrave.MusgraveData;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
@ -54,18 +55,6 @@ import java.util.logging.Logger;
/*package*/ class NoiseGenerator extends AbstractBlenderHelper { /*package*/ class NoiseGenerator extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName()); private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName());
// flag
protected static final int TEX_COLORBAND = 1;
protected static final int TEX_FLIPBLEND = 2;
protected static final int TEX_NEGALPHA = 4;
protected static final int TEX_CHECKER_ODD = 8;
protected static final int TEX_CHECKER_EVEN = 16;
protected static final int TEX_PRV_ALPHA = 32;
protected static final int TEX_PRV_NOR = 64;
protected static final int TEX_REPEAT_XMIR = 128;
protected static final int TEX_REPEAT_YMIR = 256;
protected static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR;
// tex->stype // tex->stype
protected static final int TEX_PLASTIC = 0; protected static final int TEX_PLASTIC = 0;
protected static final int TEX_WALLIN = 1; protected static final int TEX_WALLIN = 1;
@ -575,7 +564,7 @@ import java.util.logging.Logger;
int xi = (int) FastMath.floor(x); int xi = (int) FastMath.floor(x);
int yi = (int) FastMath.floor(y); int yi = (int) FastMath.floor(y);
int zi = (int) FastMath.floor(z); int zi = (int) FastMath.floor(z);
da[0] = da[1] = da[2] = da[3] = 1e10f; da[0] = da[1] = da[2] = da[3] = Float.MAX_VALUE;//1e10f;
for (int i = xi - 1; i <= xi + 1; ++i) { for (int i = xi - 1; i <= xi + 1; ++i) {
for (int j = yi - 1; j <= yi + 1; ++j) { for (int j = yi - 1; j <= yi + 1; ++j) {
for (int k = zi - 1; k <= zi + 1; ++k) { for (int k = zi - 1; k <= zi + 1; ++k) {

@ -0,0 +1,129 @@
/*
* 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.generating;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.ColorBand;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image.Format;
/**
* This class is a base class for texture generators.
* @author Marcin Roguski (Kaelthas)
*/
public abstract class TextureGenerator {
protected NoiseGenerator noiseGenerator;
protected int flag;
protected float[][] colorBand;
protected BrightnessAndContrastData bacd;
protected Format imageFormat;
public TextureGenerator(NoiseGenerator noiseGenerator, Format imageFormat) {
this.noiseGenerator = noiseGenerator;
this.imageFormat = imageFormat;
}
public Format getImageFormat() {
return imageFormat;
}
public void readData(Structure tex, BlenderContext blenderContext) {
flag = ((Number) tex.getFieldValue("flag")).intValue();
colorBand = new ColorBand(tex, blenderContext).computeValues();
bacd = new BrightnessAndContrastData(tex);
if(colorBand != null) {
imageFormat = Format.RGBA8;
}
}
public abstract void getPixel(TexturePixel pixel, float x, float y, float z);
/**
* 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;
}
}
/**
* 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();
}
}
}

@ -29,18 +29,13 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.scene.plugins.blender.textures; package com.jme3.scene.plugins.blender.textures.generating;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.texture.Image; import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/** /**
* This class generates the 'blend' texture. * This class generates the 'blend' texture.
@ -110,56 +105,31 @@ public final class TextureGeneratorBlend extends TextureGenerator {
* the noise generator * the noise generator
*/ */
public TextureGeneratorBlend(NoiseGenerator noiseGenerator) { public TextureGeneratorBlend(NoiseGenerator noiseGenerator) {
super(noiseGenerator); super(noiseGenerator, Format.Luminance8);
} }
@Override protected int stype;
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
int flag = ((Number) tex.getFieldValue("flag")).intValue();
int stype = ((Number) tex.getFieldValue("stype")).intValue();
TexturePixel texres = new TexturePixel();
int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD, x, y;
float[][] colorBand = this.computeColorband(tex, blenderContext);
BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
int bytesPerPixel = colorBand != null ? 4 : 1;
boolean flipped = (flag & NoiseGenerator.TEX_FLIPBLEND) != 0;
byte[] data = new byte[width * height * depth * bytesPerPixel]; @Override
for (int i = -halfW; i < halfW; ++i) { public void readData(Structure tex, BlenderContext blenderContext) {
x = wDelta * i; super.readData(tex, blenderContext);
for (int j = -halfH; j < halfH; ++j) { stype = ((Number) tex.getFieldValue("stype")).intValue();
if (flipped) {
y = x;
x = hDelta * j;
} else {
y = hDelta * j;
} }
for (int k = -halfD; k < halfD; ++k) {
texres.intensity = INTENSITY_FUNCTION[stype].getIntensity(x, y, dDelta * k); @Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
pixel.intensity = INTENSITY_FUNCTION[stype].getIntensity(x, y, z);
if (colorBand != null) { if (colorBand != null) {
int colorbandIndex = (int) (texres.intensity * 1000.0f); int colorbandIndex = (int) (pixel.intensity * 1000.0f);
texres.red = colorBand[colorbandIndex][0]; pixel.red = colorBand[colorbandIndex][0];
texres.green = colorBand[colorbandIndex][1]; pixel.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2]; pixel.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, texres); this.applyBrightnessAndContrast(bacd, pixel);
data[index++] = (byte) (texres.red * 255.0f);
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
} else { } else {
this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness); this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
data[index++] = (byte) (texres.intensity * 255.0f);
}
}
}
} }
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(format, width, height, depth, dataArray));
} }
private static interface IntensityFunction { private static interface IntensityFunction {

@ -0,0 +1,109 @@
/*
* 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.generating;
import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image.Format;
/**
* This class generates the 'clouds' texture.
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorClouds extends TextureGenerator {
// noiseType
protected static final int TEX_NOISESOFT = 0;
protected static final int TEX_NOISEPERL = 1;
// sType
protected static final int TEX_DEFAULT = 0;
protected static final int TEX_COLOR = 1;
protected float noisesize;
protected int noiseDepth;
protected int noiseBasis;
protected int noiseType;
protected boolean isHard;
protected int sType;
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorClouds(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.Luminance8);
}
@Override
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext);
noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
noiseType = ((Number) tex.getFieldValue("noisetype")).intValue();
isHard = noiseType != TEX_NOISESOFT;
sType = ((Number) tex.getFieldValue("stype")).intValue();
if(sType == TEX_COLOR) {
this.imageFormat = Format.RGBA8;
}
}
@Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
pixel.intensity = NoiseGenerator.NoiseFunctions.turbulence(x, y, z, noisesize, noiseDepth, noiseBasis, isHard);
pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f);
if (colorBand != null) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
pixel.alpha = colorBand[colorbandIndex][3];
this.applyBrightnessAndContrast(bacd, pixel);
} else if (sType == TEX_COLOR) {
pixel.red = pixel.intensity;
pixel.green = NoiseGenerator.NoiseFunctions.turbulence(y, x, z, noisesize, noiseDepth, noiseBasis, isHard);
pixel.blue = NoiseGenerator.NoiseFunctions.turbulence(y, z, x, noisesize, noiseDepth, noiseBasis, isHard);
pixel.green = FastMath.clamp(pixel.green, 0.0f, 1.0f);
pixel.blue = FastMath.clamp(pixel.blue, 0.0f, 1.0f);
pixel.alpha = 1.0f;
this.applyBrightnessAndContrast(bacd, pixel);
} else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
}

@ -29,25 +29,24 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.scene.plugins.blender.textures; package com.jme3.scene.plugins.blender.textures.generating;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.NoiseGenerator.NoiseFunction; import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image; import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/** /**
* This class generates the 'distorted noise' texture. * This class generates the 'distorted noise' texture.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureGeneratorDistnoise extends TextureGenerator { public class TextureGeneratorDistnoise extends TextureGenerator {
protected float noisesize;
protected float distAmount;
protected int noisebasis;
protected int noisebasis2;
/** /**
* Constructor stores the given noise generator. * Constructor stores the given noise generator.
@ -55,55 +54,32 @@ public class TextureGeneratorDistnoise extends TextureGenerator {
* the noise generator * the noise generator
*/ */
public TextureGeneratorDistnoise(NoiseGenerator noiseGenerator) { public TextureGeneratorDistnoise(NoiseGenerator noiseGenerator) {
super(noiseGenerator); super(noiseGenerator, Format.Luminance8);
} }
@Override @Override
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) { public void readData(Structure tex, BlenderContext blenderContext) {
float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); super.readData(tex, blenderContext);
float distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue(); noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue();
int noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue(); noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue();
TexturePixel texres = new TexturePixel(); }
float[] texvec = new float[] { 0, 0, 0 };
int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
float[][] colorBand = this.computeColorband(tex, blenderContext);
Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
int bytesPerPixel = colorBand != null ? 4 : 1;
BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
byte[] data = new byte[width * height * depth * bytesPerPixel]; @Override
for (int i = -halfW; i < halfW; ++i) { public void getPixel(TexturePixel pixel, float x, float y, float z) {
texvec[0] = wDelta * i / noisesize; pixel.intensity = this.musgraveVariableLunacrityNoise(x * 4, y * 4, z * 4, distAmount, noisebasis, noisebasis2);
for (int j = -halfH; j < halfH; ++j) { pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f);
texvec[1] = hDelta * j / noisesize;
for (int k = -halfD; k < halfD; ++k) {
texvec[2] = dDelta * k;
texres.intensity = this.musgraveVariableLunacrityNoise(texvec[0], texvec[1], texvec[2], distAmount, noisebasis, noisebasis2);
texres.intensity = FastMath.clamp(texres.intensity, 0.0f, 1.0f);
if (colorBand != null) { if (colorBand != null) {
int colorbandIndex = (int) (texres.intensity * 1000.0f); int colorbandIndex = (int) (pixel.intensity * 1000.0f);
texres.red = colorBand[colorbandIndex][0]; pixel.red = colorBand[colorbandIndex][0];
texres.green = colorBand[colorbandIndex][1]; pixel.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2]; pixel.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, texres); this.applyBrightnessAndContrast(bacd, pixel);
data[index++] = (byte) (texres.red * 255.0f);
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
} else { } else {
this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness); this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
data[index++] = (byte) (texres.intensity * 255.0f);
}
}
}
} }
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(format, width, height, depth, dataArray));
} }
/** /**

@ -0,0 +1,39 @@
package com.jme3.scene.plugins.blender.textures.generating;
import com.jme3.scene.plugins.blender.textures.TextureHelper;
public class TextureGeneratorFactory {
private NoiseGenerator noiseGenerator;
public TextureGeneratorFactory(String blenderVersion) {
noiseGenerator = new NoiseGenerator(blenderVersion);
}
public TextureGenerator createTextureGenerator(int generatedTexture) {
switch(generatedTexture) {
case TextureHelper.TEX_BLEND:
return new TextureGeneratorBlend(noiseGenerator);
case TextureHelper.TEX_CLOUDS:
return new TextureGeneratorClouds(noiseGenerator);
case TextureHelper.TEX_DISTNOISE:
return new TextureGeneratorDistnoise(noiseGenerator);
case TextureHelper.TEX_MAGIC:
return new TextureGeneratorMagic(noiseGenerator);
case TextureHelper.TEX_MARBLE:
return new TextureGeneratorMarble(noiseGenerator);
case TextureHelper.TEX_MUSGRAVE:
return new TextureGeneratorMusgrave(noiseGenerator);
case TextureHelper.TEX_NOISE:
return new TextureGeneratorNoise(noiseGenerator);
case TextureHelper.TEX_STUCCI:
return new TextureGeneratorStucci(noiseGenerator);
case TextureHelper.TEX_VORONOI:
return new TextureGeneratorVoronoi(noiseGenerator);
case TextureHelper.TEX_WOOD:
return new TextureGeneratorWood(noiseGenerator);
default:
throw new IllegalStateException("Unknown generated texture type: " + generatedTexture);
}
}
}

@ -29,18 +29,13 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.scene.plugins.blender.textures; package com.jme3.scene.plugins.blender.textures.generating;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.texture.Image; import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/** /**
* This class generates the 'magic' texture. * This class generates the 'magic' texture.
@ -111,46 +106,40 @@ public class TextureGeneratorMagic extends TextureGenerator {
}; };
} }
protected int noisedepth;
protected float turbul;
protected float[] xyz = new float[3];
/** /**
* Constructor stores the given noise generator. * Constructor stores the given noise generator.
* @param noiseGenerator * @param noiseGenerator
* the noise generator * the noise generator
*/ */
public TextureGeneratorMagic(NoiseGenerator noiseGenerator) { public TextureGeneratorMagic(NoiseGenerator noiseGenerator) {
super(noiseGenerator); super(noiseGenerator, Format.RGBA8);
} }
@Override @Override
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) { public void readData(Structure tex, BlenderContext blenderContext) {
float xyz[] = new float[3], turb; super.readData(tex, blenderContext);
int noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
float turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f; turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f;
float[] texvec = new float[] { 0, 0, 0 }; }
TexturePixel texres = new TexturePixel();
int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
float[][] colorBand = this.computeColorband(tex, blenderContext);
BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
byte[] data = new byte[width * height * depth * 4]; @Override
for (int i = -halfW; i < halfW; ++i) { public void getPixel(TexturePixel pixel, float x, float y, float z) {
texvec[0] = wDelta * i; float turb = turbul;
for (int j = -halfH; j < halfH; ++j) { xyz[0] = (float) Math.sin((x + y + z) * 5.0f);
texvec[1] = hDelta * j; xyz[1] = (float) Math.cos((-x + y - z) * 5.0f);
for (int k = -halfD; k < halfD; ++k) { xyz[2] = -(float) Math.cos((-x - y + z) * 5.0f);
turb = turbul;
texvec[2] = dDelta * k;
xyz[0] = (float) Math.sin((texvec[0] + texvec[1] + texvec[2]) * 5.0f);
xyz[1] = (float) Math.cos((-texvec[0] + texvec[1] - texvec[2]) * 5.0f);
xyz[2] = -(float) Math.cos((-texvec[0] - texvec[1] + texvec[2]) * 5.0f);
if (colorBand != null) { if (colorBand != null) {
texres.intensity = FastMath.clamp(0.3333f * (xyz[0] + xyz[1] + xyz[2]), 0.0f, 1.0f); pixel.intensity = FastMath.clamp(0.3333f * (xyz[0] + xyz[1] + xyz[2]), 0.0f, 1.0f);
int colorbandIndex = (int) (texres.intensity * 1000.0f); int colorbandIndex = (int) (pixel.intensity * 1000.0f);
texres.red = colorBand[colorbandIndex][0]; pixel.red = colorBand[colorbandIndex][0];
texres.green = colorBand[colorbandIndex][1]; pixel.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2]; pixel.blue = colorBand[colorbandIndex][2];
texres.alpha = colorBand[colorbandIndex][3]; pixel.alpha = colorBand[colorbandIndex][3];
} else { } else {
if (noisedepth > 0) { if (noisedepth > 0) {
xyz[0] *= turb; xyz[0] *= turb;
@ -167,22 +156,12 @@ public class TextureGeneratorMagic extends TextureGenerator {
xyz[1] /= turb; xyz[1] /= turb;
xyz[2] /= turb; xyz[2] /= turb;
} }
texres.red = 0.5f - xyz[0]; pixel.red = 0.5f - xyz[0];
texres.green = 0.5f - xyz[1]; pixel.green = 0.5f - xyz[1];
texres.blue = 0.5f - xyz[2]; pixel.blue = 0.5f - xyz[2];
texres.alpha = 1.0f; pixel.alpha = 1.0f;
}
this.applyBrightnessAndContrast(bacd, texres);
data[index++] = (byte) (texres.red * 255.0f);
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (texres.alpha * 255.0f);
}
}
} }
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1); this.applyBrightnessAndContrast(bacd, pixel);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(Format.RGBA8, width, height, depth, dataArray));
} }
private static interface NoiseDepthFunction { private static interface NoiseDepthFunction {

@ -29,17 +29,11 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.scene.plugins.blender.textures; package com.jme3.scene.plugins.blender.textures.generating;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.texture.Image; import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/** /**
* This class generates the 'marble' texture. * This class generates the 'marble' texture.
@ -51,6 +45,8 @@ public class TextureGeneratorMarble extends TextureGeneratorWood {
protected static final int TEX_SHARP = 1; protected static final int TEX_SHARP = 1;
protected static final int TEX_SHARPER = 2; protected static final int TEX_SHARPER = 2;
protected MarbleData marbleData;
/** /**
* Constructor stores the given noise generator. * Constructor stores the given noise generator.
* @param noiseGenerator * @param noiseGenerator
@ -61,46 +57,25 @@ public class TextureGeneratorMarble extends TextureGeneratorWood {
} }
@Override @Override
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) { public void readData(Structure tex, BlenderContext blenderContext) {
float[] texvec = new float[] { 0, 0, 0 }; super.readData(tex, blenderContext);
TexturePixel texres = new TexturePixel(); marbleData = new MarbleData(tex);
int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0; }
float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
float[][] colorBand = this.computeColorband(tex, blenderContext);
Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
int bytesPerPixel = colorBand != null ? 4 : 1;
BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
MarbleData marbleData = new MarbleData(tex);
byte[] data = new byte[width * height * depth * bytesPerPixel]; @Override
for (int i = -halfW; i < halfW; ++i) { public void getPixel(TexturePixel pixel, float x, float y, float z) {
texvec[0] = wDelta * i; pixel.intensity = this.marbleInt(marbleData, x, y, z);
for (int j = -halfH; j < halfH; ++j) {
texvec[1] = hDelta * j;
for (int k = -halfD; k < halfD; ++k) {
texvec[2] = dDelta * k;
texres.intensity = this.marbleInt(marbleData, texvec[0], texvec[1], texvec[2]);
if (colorBand != null) { if (colorBand != null) {
int colorbandIndex = (int) (texres.intensity * 1000.0f); int colorbandIndex = (int) (pixel.intensity * 1000.0f);
texres.red = colorBand[colorbandIndex][0]; pixel.red = colorBand[colorbandIndex][0];
texres.green = colorBand[colorbandIndex][1]; pixel.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2]; pixel.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, texres); this.applyBrightnessAndContrast(bacd, pixel);
data[index++] = (byte) (texres.red * 255.0f); pixel.alpha = colorBand[colorbandIndex][3];
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
} else { } else {
this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness); this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
data[index++] = (byte) (texres.intensity * 255.0f);
}
}
}
} }
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(format, width, height, depth, dataArray));
} }
public float marbleInt(MarbleData marbleData, float x, float y, float z) { public float marbleInt(MarbleData marbleData, float x, float y, float z) {

@ -29,24 +29,23 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.scene.plugins.blender.textures; package com.jme3.scene.plugins.blender.textures.generating;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.NoiseGenerator.MusgraveFunction; import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image; import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.MusgraveFunction;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/** /**
* This class generates the 'musgrave' texture. * This class generates the 'musgrave' texture.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureGeneratorMusgrave extends TextureGenerator { public class TextureGeneratorMusgrave extends TextureGenerator {
protected MusgraveData musgraveData;
protected MusgraveFunction musgraveFunction;
protected int stype;
protected float noisesize;
/** /**
* Constructor stores the given noise generator. * Constructor stores the given noise generator.
@ -54,63 +53,41 @@ public class TextureGeneratorMusgrave extends TextureGenerator {
* the noise generator * the noise generator
*/ */
public TextureGeneratorMusgrave(NoiseGenerator noiseGenerator) { public TextureGeneratorMusgrave(NoiseGenerator noiseGenerator) {
super(noiseGenerator); super(noiseGenerator, Format.Luminance8);
} }
@Override @Override
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) { public void readData(Structure tex, BlenderContext blenderContext) {
int stype = ((Number) tex.getFieldValue("stype")).intValue(); super.readData(tex, blenderContext);
float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); musgraveData = new MusgraveData(tex);
TexturePixel texres = new TexturePixel(); stype = ((Number) tex.getFieldValue("stype")).intValue();
float[] texvec = new float[] { 0, 0, 0 }; noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
float[][] colorBand = this.computeColorband(tex, blenderContext);
Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
int bytesPerPixel = colorBand != null ? 4 : 1;
MusgraveData musgraveData = new MusgraveData(tex);
MusgraveFunction musgraveFunction;
BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
byte[] data = new byte[width * height * depth * bytesPerPixel];
for (int i = -halfW; i < halfW; ++i) {
texvec[0] = wDelta * i / noisesize;
for (int j = -halfH; j < halfH; ++j) {
texvec[1] = hDelta * j / noisesize;
for (int k = -halfD; k < halfD; ++k) {
texvec[2] = dDelta * k / noisesize;
musgraveFunction = NoiseGenerator.musgraveFunctions.get(Integer.valueOf(musgraveData.stype)); musgraveFunction = NoiseGenerator.musgraveFunctions.get(Integer.valueOf(musgraveData.stype));
if(musgraveFunction==null) { if(musgraveFunction==null) {
throw new IllegalStateException("Unknown type of musgrave texture: " + stype); throw new IllegalStateException("Unknown type of musgrave texture: " + stype);
} }
texres.intensity = musgraveData.outscale * musgraveFunction.execute(musgraveData, texvec[0], texvec[1], texvec[2]); }
if(texres.intensity>1) {
texres.intensity = 1.0f; @Override
} else if(texres.intensity < 0) { public void getPixel(TexturePixel pixel, float x, float y, float z) {
texres.intensity = 0.0f; pixel.intensity = musgraveData.outscale * musgraveFunction.execute(musgraveData, x, y, z);
if(pixel.intensity>1) {
pixel.intensity = 1.0f;
} else if(pixel.intensity < 0) {
pixel.intensity = 0.0f;
} }
if (colorBand != null) { if (colorBand != null) {
int colorbandIndex = (int) (texres.intensity * 1000.0f); int colorbandIndex = (int) (pixel.intensity * 1000.0f);
texres.red = colorBand[colorbandIndex][0]; pixel.red = colorBand[colorbandIndex][0];
texres.green = colorBand[colorbandIndex][1]; pixel.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2]; pixel.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness); this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
data[index++] = (byte) (texres.red * 255.0f); pixel.alpha = colorBand[colorbandIndex][3];
data[index++] = (byte) (texres.green * 255.0f);
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
} else { } else {
this.applyBrightnessAndContrast(bacd, texres); this.applyBrightnessAndContrast(bacd, pixel);
data[index++] = (byte) (texres.intensity * 255.0f);
}
}
}
} }
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(format, width, height, depth, dataArray));
} }
protected static class MusgraveData { protected static class MusgraveData {

@ -0,0 +1,85 @@
/*
* 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.generating;
import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image.Format;
/**
* This class generates the 'noise' texture.
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorNoise extends TextureGenerator {
protected int noisedepth;
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorNoise(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.Luminance8);
}
@Override
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext);
noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
}
@Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
int random = FastMath.rand.nextInt();
int val = random & 3;
int loop = noisedepth;
while (loop-- != 0) {
random >>= 2;
val *= random & 3;
}
pixel.intensity = FastMath.clamp(val, 0.0f, 1.0f);
if (colorBand != null) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, pixel);
pixel.alpha = colorBand[colorbandIndex][3];
} else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
}

@ -0,0 +1,101 @@
/*
* 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.generating;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image.Format;
/**
* This class generates the 'stucci' texture.
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorStucci extends TextureGenerator {
protected static final int TEX_NOISESOFT = 0;
protected float noisesize;
protected int noisebasis;
protected int noisetype;
protected float turbul;
protected boolean isHard;
protected int stype;
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorStucci(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.Luminance8);
}
@Override
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext);
noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
noisetype = ((Number) tex.getFieldValue("noisetype")).intValue();
turbul = ((Number) tex.getFieldValue("turbul")).floatValue();
isHard = noisetype != TEX_NOISESOFT;
stype = ((Number) tex.getFieldValue("stype")).intValue();
if(noisesize<=0.001f) {//the texture goes black if this value is lower than 0.001f
noisesize = 0.001f;
}
}
@Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
float noiseValue = NoiseGenerator.NoiseFunctions.noise(x, y, z, noisesize, 0, noisebasis, isHard);
float ofs = turbul / 200.0f;
if (stype != 0) {
ofs *= noiseValue * noiseValue;
}
pixel.intensity = NoiseGenerator.NoiseFunctions.noise(x, y, z + ofs, noisesize, 0, noisebasis, isHard);
if (colorBand != null) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
pixel.alpha = colorBand[colorbandIndex][3];
}
if (stype == NoiseGenerator.TEX_WALLOUT) {
pixel.intensity = 1.0f - pixel.intensity;
}
if (pixel.intensity < 0.0f) {
pixel.intensity = 0.0f;
}
//no brightness and contrast needed for stucci (it doesn't affect the texture)
}
}

@ -0,0 +1,141 @@
/*
* 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.generating;
import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseMath;
import com.jme3.texture.Image.Format;
/**
* This class generates the 'voronoi' texture.
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorVoronoi extends TextureGenerator {
protected float noisesize;
protected float outscale;
protected float mexp;
protected int distanceType;
protected int voronoiColorType;
protected float[] da = new float[4], pa = new float[12];
protected float[] hashPoint;
protected float[] voronoiWeights;
protected float weightSum;
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorVoronoi(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.Luminance8);
}
@Override
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext);
voronoiWeights = new float[4];
voronoiWeights[0] = ((Number) tex.getFieldValue("vn_w1")).floatValue();
voronoiWeights[1] = ((Number) tex.getFieldValue("vn_w2")).floatValue();
voronoiWeights[2] = ((Number) tex.getFieldValue("vn_w3")).floatValue();
voronoiWeights[3] = ((Number) tex.getFieldValue("vn_w4")).floatValue();
noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue();
mexp = ((Number) tex.getFieldValue("vn_mexp")).floatValue();
distanceType = ((Number) tex.getFieldValue("vn_distm")).intValue();
voronoiColorType = ((Number) tex.getFieldValue("vn_coltype")).intValue();
hashPoint = voronoiColorType != 0 ? new float[3] : null;
weightSum = voronoiWeights[0] + voronoiWeights[1] + voronoiWeights[2] + voronoiWeights[3];
if (weightSum != 0.0f) {
weightSum = outscale / weightSum;
}
if(voronoiColorType != 0 || colorBand != null) {
this.imageFormat = Format.RGBA8;
}
}
@Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
//for voronoi we need to widen the range a little
NoiseGenerator.NoiseFunctions.voronoi(x * 4, y * 4, z * 4, da, pa, mexp, distanceType);
pixel.intensity = weightSum * FastMath.abs(voronoiWeights[0] * da[0] + voronoiWeights[1] * da[1] + voronoiWeights[2] * da[2] + voronoiWeights[3] * da[3]);
if(pixel.intensity>1.0f) {
pixel.intensity = 1.0f;
} else if(pixel.intensity<0.0f) {
pixel.intensity = 0.0f;
}
if (colorBand != null) {//colorband ALWAYS goes first and covers the color (if set)
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
pixel.alpha = colorBand[colorbandIndex][3];
} else if (voronoiColorType != 0) {
pixel.red = pixel.green = pixel.blue = 0.0f;
pixel.alpha = 1.0f;
for(int m=0; m<12; m+=3) {
float weight = voronoiWeights[m/3];
NoiseMath.hash((int)pa[m], (int)pa[m + 1], (int)pa[m + 2], hashPoint);
pixel.red += weight * hashPoint[0];
pixel.green += weight * hashPoint[1];
pixel.blue += weight * hashPoint[2];
}
if (voronoiColorType >= 2) {
float t1 = (da[1] - da[0]) * 10.0f;
if (t1 > 1.0f) {
t1 = 1.0f;
}
if (voronoiColorType == 3) {
t1 *= pixel.intensity;
} else {
t1 *= weightSum;
}
pixel.red *= t1;
pixel.green *= t1;
pixel.blue *= t1;
} else {
pixel.red *= weightSum;
pixel.green *= weightSum;
pixel.blue *= weightSum;
}
}
if (voronoiColorType != 0 || colorBand != null) {
this.applyBrightnessAndContrast(bacd, pixel);
} else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
}

@ -28,18 +28,13 @@
* ***** END GPL LICENSE BLOCK ***** * ***** END GPL LICENSE BLOCK *****
* *
*/ */
package com.jme3.scene.plugins.blender.textures; package com.jme3.scene.plugins.blender.textures.generating;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.texture.Image; import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/** /**
* This class generates the 'wood' texture. * This class generates the 'wood' texture.
@ -61,63 +56,38 @@ public class TextureGeneratorWood extends TextureGenerator {
protected static final int TEX_NOISESOFT = 0; protected static final int TEX_NOISESOFT = 0;
protected static final int TEX_NOISEPERL = 1; protected static final int TEX_NOISEPERL = 1;
protected WoodIntensityData woodIntensityData;
/** /**
* Constructor stores the given noise generator. * Constructor stores the given noise generator.
* @param noiseGenerator the noise generator * @param noiseGenerator the noise generator
*/ */
public TextureGeneratorWood(NoiseGenerator noiseGenerator) { public TextureGeneratorWood(NoiseGenerator noiseGenerator) {
super(noiseGenerator); super(noiseGenerator, Format.Luminance8);
} }
@Override @Override
protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) { public void readData(Structure tex, BlenderContext blenderContext) {
float[] texvec = new float[] { 0, 0, 0 }; super.readData(tex, blenderContext);
TexturePixel texres = new TexturePixel(); woodIntensityData = new WoodIntensityData(tex);
int halfW = width >> 1; }
int halfH = height >> 1;
int halfD = depth >> 1;
float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
float[][] colorBand = this.computeColorband(tex, blenderContext);
Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
int bytesPerPixel = colorBand != null ? 4 : 1;
WoodIntensityData woodIntensityData = new WoodIntensityData(tex);
BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
int index = 0;
byte[] data = new byte[width * height * depth * bytesPerPixel];
for (int i = -halfW; i < halfW; ++i) {
texvec[0] = wDelta * i;
for (int j = -halfH; j < halfH; ++j) {
texvec[1] = hDelta * j;
for(int k = -halfD; k < halfD; ++k) {
texvec[2] = dDelta * k;
texres.intensity = this.woodIntensity(woodIntensityData, texvec[0], texvec[1], texvec[2]);
if (colorBand != null) { @Override
int colorbandIndex = (int) (texres.intensity * 1000.0f); public void getPixel(TexturePixel pixel, float x, float y, float z) {
texres.red = colorBand[colorbandIndex][0]; pixel.intensity = this.woodIntensity(woodIntensityData, x, y, z);
texres.green = colorBand[colorbandIndex][1];
texres.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, texres); if (colorBand != null) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
data[index++] = (byte) (texres.red * 255.0f); this.applyBrightnessAndContrast(bacd, pixel);
data[index++] = (byte) (texres.green * 255.0f); pixel.alpha = colorBand[colorbandIndex][3];
data[index++] = (byte) (texres.blue * 255.0f);
data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
} else { } else {
this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness); this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
data[index++] = (byte) (texres.intensity * 255.0f);
} }
} }
}
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
dataArray.add(BufferUtils.createByteBuffer(data));
return new Texture3D(new Image(format, width, height, depth, dataArray));
}
protected static WaveForm[] waveformFunctions = new WaveForm[3]; protected static WaveForm[] waveformFunctions = new WaveForm[3];
static { static {

@ -0,0 +1,75 @@
package com.jme3.scene.plugins.blender.textures.io;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image;
/**
* Implemens read/write operations for AWT images.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class AWTPixelInputOutput implements PixelInputOutput {
@Override
public void read(Image image, TexturePixel pixel, int index) {
byte r,g,b,a;
switch(image.getFormat()) {//TODO: add other formats
case RGBA8:
r = image.getData(0).get(index);
g = image.getData(0).get(index + 1);
b = image.getData(0).get(index + 2);
a = image.getData(0).get(index + 3);
break;
case ABGR8:
a = image.getData(0).get(index);
b = image.getData(0).get(index + 1);
g = image.getData(0).get(index + 2);
r = image.getData(0).get(index + 3);
break;
case BGR8:
b = image.getData(0).get(index);
g = image.getData(0).get(index + 1);
r = image.getData(0).get(index + 2);
a = (byte)0xFF;
break;
default:
throw new IllegalStateException("Unknown image format: " + image.getFormat());
}
pixel.fromARGB8(a, r, g, b);
}
@Override
public void read(Image image, TexturePixel pixel, int x, int y) {
int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3);
this.read(image, pixel, index);
}
@Override
public void write(Image image, TexturePixel pixel, int index) {
switch(image.getFormat()) {
case RGBA8:
image.getData(0).put(index, pixel.getR8());
image.getData(0).put(index + 1, pixel.getG8());
image.getData(0).put(index + 2, pixel.getB8());
image.getData(0).put(index + 3, pixel.getA8());
break;
case ABGR8:
image.getData(0).put(index, pixel.getA8());
image.getData(0).put(index + 1, pixel.getB8());
image.getData(0).put(index + 2, pixel.getG8());
image.getData(0).put(index + 3, pixel.getR8());
break;
case BGR8:
image.getData(0).put(index, pixel.getB8());
image.getData(0).put(index + 1, pixel.getG8());
image.getData(0).put(index + 2, pixel.getR8());
break;
default:
throw new IllegalStateException("Unknown image format: " + image.getFormat());
}
}
@Override
public void write(Image image, TexturePixel pixel, int x, int y) {
int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3);
this.write(image, pixel, index);
}
}

@ -0,0 +1,174 @@
package com.jme3.scene.plugins.blender.textures.io;
import java.nio.ByteBuffer;
import jme3tools.converters.RGB565;
import com.jme3.math.FastMath;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image;
/**
* Implemens read/write operations for DDS images.
* This class currently implements only read operation.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class DDSPixelInputOutput implements PixelInputOutput {
@Override
public void read(Image image, TexturePixel pixel, int index) {
throw new UnsupportedOperationException("Cannot get the DXT pixel by index because not every index contains the pixel color!");
}
@Override
public void read(Image image, TexturePixel pixel, int x, int y) {
int xTexetlIndex = x % image.getWidth() >> 2;
int yTexelIndex = y % image.getHeight() >> 2;
int xTexelCount = image.getWidth() >> 2;
int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex;
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
int indexes = 0;
long alphaIndexes = 0;
float[] alphas = null;
ByteBuffer data = image.getData().get(0);
switch (image.getFormat()) {
case DXT1: {// BC1
data.position(texelIndex * 8);
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);
}
indexes = data.getInt();// 4-byte table with color indexes in decompressed table
break;
}
case DXT3: {// BC2
data.position(texelIndex * 16);
long alpha = data.getLong();
alphas = new float[16];
for (int i = 0; i < 16; ++i) {
alphaIndexes |= 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);
indexes = data.getInt();// 4-byte table with color indexes in decompressed table
break;
}
case DXT5: {// BC3
data.position(texelIndex * 16);
alphas = new float[8];
alphas[0] = data.get() * 255.0f;
alphas[1] = data.get() * 255.0f;
alphaIndexes = 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);
indexes = data.getInt();// 4-byte table with color indexes in decompressed table
break;
}
case DXT1A://TODO: implement
break;
default:
throw new IllegalStateException("Unsupported decompression format.");
}
// coordinates of the pixel in the selected texel
x = x - 4 * xTexetlIndex;// pixels are arranged from left to right
y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start)
int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2);
int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0;
// getting the pixel
int indexMask = colors.length - 1;
int colorIndex = indexes >> pixelIndexInTexel & indexMask;
float alpha = alphas != null ? alphas[(int) (alphaIndexes >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha;
pixel.fromPixel(colors[colorIndex]);
pixel.alpha = alpha;
}
@Override
public void write(Image image, TexturePixel pixel, int index) {
throw new UnsupportedOperationException("Cannot put the DXT pixel by index because not every index contains the pixel color!");
}
@Override
public void write(Image image, TexturePixel pixel, int x, int y) {
throw new UnsupportedOperationException("Writing to DDS texture pixel by pixel is not yet supported!");
}
}

@ -0,0 +1,33 @@
package com.jme3.scene.plugins.blender.textures.io;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image;
/**
* Implemens read/write operations for luminance images.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class LuminancePixelInputOutput implements PixelInputOutput {
@Override
public void read(Image image, TexturePixel pixel, int index) {
byte intensity = image.getData(0).get(index);
pixel.fromIntensity(intensity);
}
@Override
public void read(Image image, TexturePixel pixel, int x, int y) {
int index = y * image.getWidth() + x;
this.read(image, pixel, index);
}
@Override
public void write(Image image, TexturePixel pixel, int index) {
image.getData(0).put(index, pixel.getInt());
}
@Override
public void write(Image image, TexturePixel pixel, int x, int y) {
int index = y * image.getWidth() + x;
this.write(image, pixel, index);
}
}

@ -0,0 +1,50 @@
package com.jme3.scene.plugins.blender.textures.io;
import java.util.HashMap;
import java.util.Map;
import com.jme3.texture.Image.Format;
/**
* This class creates a pixel IO object for the specified image format.
*
* @author Marcin Roguski (Kaelthas)
*/
public class PixelIOFactory {
private static final Map<Format, PixelInputOutput> PIXEL_INPUT_OUTPUT = new HashMap<Format, PixelInputOutput>();
/**
* This method returns pixel IO object for the specified format.
*
* @param format
* the format of the image
* @return pixel IO object
*/
public static PixelInputOutput getPixelIO(Format format) {
PixelInputOutput result = PIXEL_INPUT_OUTPUT.get(format);
if (result == null) {
switch (format) {
case ABGR8:
case RGBA8:
case BGR8:
result = new AWTPixelInputOutput();
break;
case Luminance8:
result = new LuminancePixelInputOutput();
break;
case DXT1:
case DXT1A:
case DXT3:
case DXT5:
result = new DDSPixelInputOutput();
break;
default:
throw new IllegalStateException("Unsupported image format for IO operations: " + format);
}
synchronized (PIXEL_INPUT_OUTPUT) {
PIXEL_INPUT_OUTPUT.put(format, result);
}
}
return result;
}
}

@ -0,0 +1,65 @@
package com.jme3.scene.plugins.blender.textures.io;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.texture.Image;
/**
* Implemens read/write operations for images.
*
* @author Marcin Roguski (Kaelthas)
*/
public interface PixelInputOutput {
/**
* This method reads a pixel that starts at the given index.
*
* @param image
* the image we read pixel from
* @param pixel
* the pixel where the result is stored
* @param index
* the index where the pixel begins in the image data
*/
void read(Image image, TexturePixel pixel, int index);
/**
* This method reads a pixel that is located at the given position on the
* image.
*
* @param image
* the image we read pixel from
* @param pixel
* the pixel where the result is stored
* @param x
* the X coordinate of the pixel
* @param y
* the Y coordinate of the pixel
*/
void read(Image image, TexturePixel pixel, int x, int y);
/**
* This method writes a pixel that starts at the given index.
*
* @param image
* the image we read pixel from
* @param pixel
* the pixel where the result is stored
* @param index
* the index where the pixel begins in the image data
*/
void write(Image image, TexturePixel pixel, int index);
/**
* This method writes a pixel that is located at the given position on the
* image.
*
* @param image
* the image we read pixel from
* @param pixel
* the pixel where the result is stored
* @param x
* the X coordinate of the pixel
* @param y
* the Y coordinate of the pixel
*/
void write(Image image, TexturePixel pixel, int x, int y);
}
Loading…
Cancel
Save