diff --git a/engine/src/blender/com/jme3/asset/BlenderKey.java b/engine/src/blender/com/jme3/asset/BlenderKey.java index a283e824d..151cd4d38 100644 --- a/engine/src/blender/com/jme3/asset/BlenderKey.java +++ b/engine/src/blender/com/jme3/asset/BlenderKey.java @@ -46,9 +46,9 @@ import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; -import com.jme3.light.AmbientLight; import com.jme3.material.Material; import com.jme3.material.RenderState.FaceCullMode; +import com.jme3.math.ColorRGBA; import com.jme3.scene.CameraNode; import com.jme3.scene.LightNode; import com.jme3.scene.Node; @@ -63,24 +63,24 @@ import com.jme3.texture.Texture; */ public class BlenderKey extends ModelKey { - protected static final int DEFAULT_FPS = 25; + protected static final int DEFAULT_FPS = 25; /** * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time * between the frames. */ - protected int fps = DEFAULT_FPS; + protected int fps = DEFAULT_FPS; /** * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. */ - protected int featuresToLoad = FeaturesToLoad.ALL; + protected int featuresToLoad = FeaturesToLoad.ALL; /** This variable determines if assets that are not linked to the objects should be loaded. */ protected boolean loadUnlinkedAssets; /** The root path for all the assets. */ protected String assetRootPath; /** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */ - protected boolean fixUpAxis = true; + protected boolean fixUpAxis = true; /** Generated textures resolution (PPU - Pixels Per Unit). */ - protected int generatedTexturePPU = 128; + protected int generatedTexturePPU = 128; /** * The name of world settings that the importer will use. If not set or specified name does not occur in the file * then the first world settings in the file will be used. @@ -92,20 +92,25 @@ public class BlenderKey extends ModelKey { */ protected Material defaultMaterial; /** Face cull mode. By default it is disabled. */ - protected FaceCullMode faceCullMode = FaceCullMode.Back; + protected FaceCullMode faceCullMode = FaceCullMode.Back; /** * Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded. * If set to -1 then the current layer will be loaded. */ - protected int layersToLoad = -1; + protected int layersToLoad = -1; /** A variable that toggles the object custom properties loading. */ - protected boolean loadObjectProperties = true; + protected boolean loadObjectProperties = true; /** Maximum texture size. Might be dependant on the graphic card. */ - protected int maxTextureSize = -1; + protected int maxTextureSize = -1; /** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */ protected boolean loadGeneratedTextures; /** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */ - protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED; + protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED; + /** + * If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated + * textures will get their proper size. + */ + protected int skyGeneratedTextureSize = 1000; /** * Constructor used by serialization mechanisms. @@ -356,6 +361,24 @@ public class BlenderKey extends ModelKey { this.mipmapGenerationMethod = mipmapGenerationMethod; } + /** + * @return the size of the generated textures for the sky (used if no flat textures are applied) + */ + public int getSkyGeneratedTextureSize() { + return skyGeneratedTextureSize; + } + + /** + * @param skyGeneratedTextureSize + * the size of the generated textures for the sky (used if no flat textures are applied) + */ + public void setSkyGeneratedTextureSize(int skyGeneratedTextureSize) { + if (skyGeneratedTextureSize <= 0) { + throw new IllegalArgumentException("The texture size must be a positive value (the value given as a parameter: " + skyGeneratedTextureSize + ")!"); + } + this.skyGeneratedTextureSize = skyGeneratedTextureSize; + } + /** * This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is * not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used @@ -521,6 +544,7 @@ public class BlenderKey extends ModelKey { int TEXTURES = 0x00000001; int CAMERAS = 0x00000020; int LIGHTS = 0x00000010; + int WORLD = 0x00000040; int ALL = 0xFFFFFFFF; } @@ -531,21 +555,28 @@ public class BlenderKey extends ModelKey { public static class LoadingResults extends Spatial { /** Bitwise mask of features that are to be loaded. */ - private final int featuresToLoad; + private final int featuresToLoad; /** The scenes from the file. */ - private List scenes; + private List scenes; /** Objects from all scenes. */ - private List objects; + private List objects; /** Materials from all objects. */ - private List materials; + private List materials; /** Textures from all objects. */ - private List textures; + private List textures; /** Animations of all objects. */ - private List animations; + private List animations; /** All cameras from the file. */ - private List cameras; + private List cameras; /** All lights from the file. */ - private List lights; + private List lights; + /** Loaded sky. */ + private Spatial sky; + /** + * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color + * is set to default (as in blender editor. + */ + private ColorRGBA backgroundColor = ColorRGBA.Gray; /** * Private constructor prevents users to create an instance of this class from outside the @@ -654,7 +685,23 @@ public class BlenderKey extends ModelKey { } /** - * This method returns all loaded scenes. + * This method sets the sky of the scene. Only one sky can be set. + * @param sky + * the sky to be set + */ + public void setSky(Spatial sky) { + this.sky = sky; + } + + /** + * @param backgroundColor + * the background color + */ + public void setBackgroundColor(ColorRGBA backgroundColor) { + this.backgroundColor = backgroundColor; + } + + /** * @return all loaded scenes */ public List getScenes() { @@ -662,7 +709,6 @@ public class BlenderKey extends ModelKey { } /** - * This method returns all loaded objects. * @return all loaded objects */ public List getObjects() { @@ -670,7 +716,6 @@ public class BlenderKey extends ModelKey { } /** - * This method returns all loaded materials. * @return all loaded materials */ public List getMaterials() { @@ -678,7 +723,6 @@ public class BlenderKey extends ModelKey { } /** - * This method returns all loaded textures. * @return all loaded textures */ public List getTextures() { @@ -686,7 +730,6 @@ public class BlenderKey extends ModelKey { } /** - * This method returns all loaded animations. * @return all loaded animations */ public List getAnimations() { @@ -694,7 +737,6 @@ public class BlenderKey extends ModelKey { } /** - * This method returns all loaded cameras. * @return all loaded cameras */ public List getCameras() { @@ -702,13 +744,26 @@ public class BlenderKey extends ModelKey { } /** - * This method returns all loaded lights. * @return all loaded lights */ public List getLights() { return lights; } + /** + * @return the scene's sky + */ + public Spatial getSky() { + return sky; + } + + /** + * @return the background color + */ + public ColorRGBA getBackgroundColor() { + return backgroundColor; + } + public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { return 0; } @@ -744,32 +799,4 @@ public class BlenderKey extends ModelKey { protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) { } } - - /** - * The WORLD file block contains various data that could be added to the scene. The contained data includes: ambient - * light. - * @author Marcin Roguski (Kaelthas) - */ - public static class WorldData { - - /** The ambient light. */ - private AmbientLight ambientLight; - - /** - * This method returns the world's ambient light. - * @return the world's ambient light - */ - public AmbientLight getAmbientLight() { - return ambientLight; - } - - /** - * This method sets the world's ambient light. - * @param ambientLight - * the world's ambient light - */ - public void setAmbientLight(AmbientLight ambientLight) { - this.ambientLight = ambientLight; - } - } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java index 29d671a80..8b68e76ae 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java @@ -37,9 +37,6 @@ import java.util.logging.Logger; import com.jme3.asset.AssetLoader; import com.jme3.asset.BlenderKey.FeaturesToLoad; -import com.jme3.asset.BlenderKey.WorldData; -import com.jme3.light.AmbientLight; -import com.jme3.math.ColorRGBA; import com.jme3.scene.CameraNode; import com.jme3.scene.Geometry; import com.jme3.scene.LightNode; @@ -166,25 +163,4 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; // } // return null; // } - - /** - * This method returns the data read from the WORLD file block. The block contains data that can be stored as - * separate jme features and therefore cannot be returned as a single jME scene feature. - * @param structure - * the structure with WORLD block data - * @return data read from the WORLD block that can be added to the scene - */ - public WorldData toWorldData(Structure structure) { - WorldData result = new WorldData(); - - // reading ambient light - AmbientLight ambientLight = new AmbientLight(); - float ambr = ((Number) structure.getFieldValue("ambr")).floatValue(); - float ambg = ((Number) structure.getFieldValue("ambg")).floatValue(); - float ambb = ((Number) structure.getFieldValue("ambb")).floatValue(); - ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f)); - result.setAmbientLight(ambientLight); - - return result; - } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java index a88dfa5c4..30a55601e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java @@ -41,7 +41,6 @@ import com.jme3.asset.AssetInfo; import com.jme3.asset.BlenderKey; import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.asset.BlenderKey.LoadingResults; -import com.jme3.asset.BlenderKey.WorldData; import com.jme3.asset.ModelKey; import com.jme3.scene.CameraNode; import com.jme3.scene.LightNode; @@ -56,6 +55,7 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.landscape.LandscapeHelper; import com.jme3.scene.plugins.blender.lights.LightHelper; import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.scene.plugins.blender.meshes.MeshHelper; @@ -82,7 +82,6 @@ public class BlenderLoader extends AbstractBlenderLoader { List sceneBlocks = new ArrayList(); BlenderKey blenderKey = blenderContext.getBlenderKey(); LoadingResults loadingResults = blenderKey.prepareLoadingResults(); - WorldData worldData = null;// a set of data used in different scene aspects for (FileBlockHeader block : blocks) { switch (block.getCode()) { case FileBlockHeader.BLOCK_OB00:// Object @@ -109,14 +108,14 @@ public class BlenderLoader extends AbstractBlenderLoader { } break; case FileBlockHeader.BLOCK_WO00:// World - if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used + if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.WORLD) != 0) { Structure worldStructure = block.getStructure(blenderContext); String worldName = worldStructure.getName(); if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { - worldData = this.toWorldData(worldStructure); - if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { - loadingResults.addLight(worldData.getAmbientLight()); - } + LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class); + loadingResults.addLight(landscapeHelper.toAmbientLight(worldStructure)); + loadingResults.setSky(landscapeHelper.toSky(worldStructure)); + loadingResults.setBackgroundColor(landscapeHelper.toBackgroundColor(worldStructure)); } } break; @@ -206,7 +205,8 @@ public class BlenderLoader extends AbstractBlenderLoader { blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext)); - + blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext)); + // reading the blocks (dna block is automatically saved in the blender context when found) FileBlockHeader sceneFileBlock = null; do { diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java new file mode 100644 index 000000000..3449ac19a --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java @@ -0,0 +1,184 @@ +package com.jme3.scene.plugins.blender.landscape; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.light.AmbientLight; +import com.jme3.light.Light; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.ColorBand; +import com.jme3.scene.plugins.blender.textures.CombinedTexture; +import com.jme3.scene.plugins.blender.textures.ImageUtils; +import com.jme3.scene.plugins.blender.textures.TextureHelper; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.TextureCubeMap; +import com.jme3.util.SkyFactory; + +/** + * The class that allows to load the following:
  • the ambient light of the scene
  • the sky of the scene (with or without texture) + * + * @author Marcin Roguski (Kaelthas) + */ +public class LandscapeHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(LandscapeHelper.class.getName()); + + private static final int SKYTYPE_BLEND = 1; + private static final int SKYTYPE_REAL = 2; + private static final int SKYTYPE_PAPER = 4; + + public LandscapeHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * Loads scene ambient light. + * @param worldStructure + * the world's blender structure + * @return the scene's ambient light + */ + public Light toAmbientLight(Structure worldStructure) { + LOGGER.fine("Loading ambient light."); + AmbientLight ambientLight = new AmbientLight(); + float ambr = ((Number) worldStructure.getFieldValue("ambr")).floatValue(); + float ambg = ((Number) worldStructure.getFieldValue("ambg")).floatValue(); + float ambb = ((Number) worldStructure.getFieldValue("ambb")).floatValue(); + ColorRGBA ambientLightColor = new ColorRGBA(ambr, ambg, ambb, 0.0f); + ambientLight.setColor(ambientLightColor); + LOGGER.log(Level.FINE, "Loaded ambient light: {0}.", ambientLightColor); + return ambientLight; + } + + /** + * Loads the background color. + * @param worldStructure + * the world's structure + * @return the horizon color of the world which is used as a background color. + */ + public ColorRGBA toBackgroundColor(Structure worldStructure) { + float horr = ((Number) worldStructure.getFieldValue("horr")).floatValue(); + float horg = ((Number) worldStructure.getFieldValue("horg")).floatValue(); + float horb = ((Number) worldStructure.getFieldValue("horb")).floatValue(); + return new ColorRGBA(horr, horg, horb, 1); + } + + /** + * Loads scene's sky. Sky can be plain or textured. + * If no sky type is selected in blender then no sky is loaded. + * @param worldStructure + * the world's structure + * @return the scene's sky + * @throws BlenderFileException + * blender exception is thrown when problems with blender file occur + */ + public Spatial toSky(Structure worldStructure) throws BlenderFileException { + int skytype = ((Number) worldStructure.getFieldValue("skytype")).intValue(); + if (skytype == 0) { + return null; + } + + LOGGER.fine("Loading sky."); + ColorRGBA horizontalColor = this.toBackgroundColor(worldStructure); + + float zenr = ((Number) worldStructure.getFieldValue("zenr")).floatValue(); + float zeng = ((Number) worldStructure.getFieldValue("zeng")).floatValue(); + float zenb = ((Number) worldStructure.getFieldValue("zenb")).floatValue(); + ColorRGBA zenithColor = new ColorRGBA(zenr, zeng, zenb, 1); + + // jutr for this case load generated textures wheather user had set it or not because those might be needed to properly load the sky + boolean loadGeneratedTextures = blenderContext.getBlenderKey().isLoadGeneratedTextures(); + blenderContext.getBlenderKey().setLoadGeneratedTextures(true); + + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + Map loadedTextures = null; + try { + loadedTextures = textureHelper.readTextureData(worldStructure, new float[] { horizontalColor.r, horizontalColor.g, horizontalColor.b, horizontalColor.a }, true); + } finally { + blenderContext.getBlenderKey().setLoadGeneratedTextures(loadGeneratedTextures); + } + + TextureCubeMap texture = null; + if (loadedTextures != null && loadedTextures.size() > 0) { + if (loadedTextures.size() > 1) { + throw new IllegalStateException("There should be only one combined texture for sky!"); + } + CombinedTexture combinedTexture = loadedTextures.get(1); + texture = combinedTexture.generateSkyTexture(horizontalColor, zenithColor, blenderContext); + } else { + LOGGER.fine("Preparing colors for colorband."); + int colorbandType = ColorBand.IPO_CARDINAL; + List colorbandColors = new ArrayList(3); + colorbandColors.add(horizontalColor); + if ((skytype & SKYTYPE_BLEND) != 0) { + if ((skytype & SKYTYPE_PAPER) != 0) { + colorbandType = ColorBand.IPO_LINEAR; + } + if ((skytype & SKYTYPE_REAL) != 0) { + colorbandColors.add(0, zenithColor); + } + colorbandColors.add(zenithColor); + } + + int size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize(); + + List positions = new ArrayList(colorbandColors.size()); + positions.add(0); + if (colorbandColors.size() == 2) { + positions.add(size - 1); + } else if (colorbandColors.size() == 3) { + positions.add(size / 2); + positions.add(size - 1); + } + + LOGGER.fine("Generating sky texture."); + float[][] values = new ColorBand(colorbandType, colorbandColors, positions, size).computeValues(); + + Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6); + PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel pixel = new TexturePixel(); + + LOGGER.fine("Creating side textures."); + int[] sideImagesIndexes = new int[] { 0, 1, 4, 5 }; + for (int i : sideImagesIndexes) { + for (int y = 0; y < size; ++y) { + pixel.red = values[y][0]; + pixel.green = values[y][1]; + pixel.blue = values[y][2]; + + for (int x = 0; x < size; ++x) { + pixelIO.write(image, i, pixel, x, y); + } + } + } + + LOGGER.fine("Creating top texture."); + pixelIO.read(image, 0, pixel, 0, image.getHeight() - 1); + for (int y = 0; y < size; ++y) { + for (int x = 0; x < size; ++x) { + pixelIO.write(image, 3, pixel, x, y); + } + } + + texture = new TextureCubeMap(image); + } + + LOGGER.fine("Sky texture created. Creating sky."); + return SkyFactory.createSky(blenderContext.getAssetManager(), texture, false); + } + + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + return true; + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java index 986deb1f0..ca8377cd6 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java @@ -1,7 +1,5 @@ package com.jme3.scene.plugins.blender.materials; -import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -21,15 +19,11 @@ import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader; import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader; import com.jme3.scene.plugins.blender.textures.CombinedTexture; import com.jme3.scene.plugins.blender.textures.TextureHelper; -import com.jme3.scene.plugins.blender.textures.blending.TextureBlender; -import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory; import com.jme3.texture.Texture; import com.jme3.util.BufferUtils; @@ -63,7 +57,6 @@ public final class MaterialContext { /* package */final boolean vTangent; /* package */FaceCullMode faceCullMode; - @SuppressWarnings("unchecked") /* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { name = structure.getName(); @@ -101,56 +94,8 @@ public final class MaterialContext { ambientColor = new ColorRGBA(r, g, b, alpha); } - DynamicArray mtexsArray = (DynamicArray) structure.getFieldValue("mtex"); - int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue(); - List texturesList = new ArrayList(); - for (int i = 0; i < mtexsArray.getTotalSize(); ++i) { - Pointer p = mtexsArray.get(i); - if (p.isNotNull() && (separatedTextures & 1 << i) == 0) { - TextureData textureData = new TextureData(); - textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0); - textureData.uvCoordinatesType = ((Number) textureData.mtex.getFieldValue("texco")).intValue(); - textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue(); - textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString(); - if(textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) { - textureData.uvCoordinatesName = null; - } - - Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex"); - if (pTex.isNotNull()) { - Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0); - textureData.textureStructure = tex; - texturesList.add(textureData); - } - } - } - - // loading the textures and merging them - Map> textureDataMap = this.sortAndFilterTextures(texturesList); - loadedTextures = new HashMap(); - float[] diffuseColorArray = new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }; TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - for (Entry> entry : textureDataMap.entrySet()) { - if (entry.getValue().size() > 0) { - CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue()); - for (TextureData textureData : entry.getValue()) { - int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); - boolean negateTexture = (texflag & 0x04) != 0; - Texture texture = textureHelper.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); - if (texture != null) { - int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); - float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; - float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); - TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac); - combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, - textureData.textureStructure, textureData.uvCoordinatesName, blenderContext); - } - } - if (combinedTexture.getTexturesCount() > 0) { - loadedTextures.put(entry.getKey(), combinedTexture); - } - } - } + loadedTextures = textureHelper.readTextureData(structure, new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }, false); // veryfying if the transparency is present // (in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when @@ -301,34 +246,6 @@ public final class MaterialContext { return false; } - /** - * This method sorts the textures by their mapping type. In each group only - * textures of one type are put (either two- or three-dimensional). If the - * mapping type is MTEX_COL then if the texture has no alpha channel then - * all textures before it are discarded and will not be loaded and merged - * because texture with no alpha will cover them anyway. - * - * @return a map with sorted and filtered textures - */ - private Map> sortAndFilterTextures(List textures) { - int[] mappings = new int[] { MTEX_COL, MTEX_NOR, MTEX_EMIT, MTEX_SPEC, MTEX_ALPHA, MTEX_AMB }; - Map> result = new HashMap>(); - for (TextureData data : textures) { - Number mapto = (Number) data.mtex.getFieldValue("mapto"); - for (int i = 0; i < mappings.length; ++i) { - if ((mappings[i] & mapto.intValue()) != 0) { - List datas = result.get(mappings[i]); - if (datas == null) { - datas = new ArrayList(); - result.put(mappings[i], datas); - } - datas.add(data); - } - } - } - return result; - } - /** * This method sets the face cull mode. * @param faceCullMode @@ -393,13 +310,4 @@ public final class MaterialContext { float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); return new ColorRGBA(r, g, b, alpha); } - - private static class TextureData { - Structure mtex; - Structure textureStructure; - int uvCoordinatesType; - int projectionType; - /** The name of the user's UV coordinates that are used for this texture. */ - String uvCoordinatesName; - } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java index 52fa3983a..4c029903d 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java @@ -31,6 +31,7 @@ */ package com.jme3.scene.plugins.blender.textures; +import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.BlenderFileException; @@ -38,6 +39,7 @@ import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; @@ -59,8 +61,46 @@ public class ColorBand { public static final int IPO_CONSTANT = 4; private int cursorsAmount, ipoType; + /** The default amount of possible cursor positions. */ + private int resultSize = 1001; private ColorBandData[] data; + /** + * A constructor used to instantiate color band by hand instead of reading it from the blend file. + * @param ipoType + * the interpolation type + * @param colors + * the colorband colors + * @param positions + * the positions for colors' cursors + * @param resultSize + * the size of the result table + */ + public ColorBand(int ipoType, List colors, List positions, int resultSize) { + if (colors == null || colors.size() < 1) { + throw new IllegalArgumentException("The amount of colorband's colors must be at least 1."); + } + if (ipoType < IPO_LINEAR || ipoType > IPO_CONSTANT) { + throw new IllegalArgumentException("Unknown colorband interpolation type: " + ipoType); + } + if (positions == null || positions.size() != colors.size()) { + throw new IllegalArgumentException("The size of positions and colors list should be equal!"); + } + for (Integer position : positions) { + if (position.intValue() < 0 || position.intValue() >= resultSize) { + throw new IllegalArgumentException("Invalid position value: " + position + "! Should be from range: [0, " + resultSize + "]!"); + } + } + + cursorsAmount = colors.size(); + this.ipoType = ipoType; + this.resultSize = resultSize; + data = new ColorBandData[this.cursorsAmount]; + for (int i = 0; i < cursorsAmount; ++i) { + data[i] = new ColorBandData(colors.get(i), positions.get(i)); + } + } + /** * Constructor. Loads the data from the given structure. * @param tex @@ -113,11 +153,8 @@ public class ColorBand { public float[][] computeValues() { float[][] result = null; if (data != null) { - result = new float[1001][4];// 1001 - amount of possible cursor - // positions; 4 = [r, g, b, a] - - if (data.length == 1) {// special case; use only one color for all - // types of colorband interpolation + result = new float[resultSize][4];// resultSize - amount of possible cursor positions; 4 = [r, g, b, a] + if (data.length == 1) {// special case; use only one color for all types of colorband interpolation for (int i = 0; i < result.length; ++i) { result[i][0] = data[0].r; result[i][1] = data[0].g; @@ -167,7 +204,7 @@ public class ColorBand { if (data[0].pos == 0) { cbDataMap.put(Integer.valueOf(-1), data[0]); } else { - ColorBandData cbData = data[0].clone(); + ColorBandData cbData = new ColorBandData(data[0]); cbData.pos = 0; cbDataMap.put(Integer.valueOf(-1), cbData); cbDataMap.put(Integer.valueOf(-2), cbData); @@ -176,7 +213,7 @@ public class ColorBand { if (data[data.length - 1].pos == 1000) { cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]); } else { - ColorBandData cbData = data[data.length - 1].clone(); + ColorBandData cbData = new ColorBandData(data[data.length - 1]); cbData.pos = 1000; cbDataMap.put(Integer.valueOf(data.length), cbData); cbDataMap.put(Integer.valueOf(data.length + 1), cbData); @@ -307,7 +344,7 @@ public class ColorBand { * * @author Marcin Roguski (Kaelthas) */ - private static class ColorBandData implements Cloneable { + private static class ColorBandData { public final float r, g, b, a; public int pos; @@ -316,6 +353,21 @@ public class ColorBand { a = 1; } + /** + * Constructor that stores the color and position of the cursor. + * @param color + * the cursor's color + * @param pos + * the cursor's position + */ + public ColorBandData(ColorRGBA color, int pos) { + r = color.r; + g = color.g; + b = color.b; + a = color.a; + this.pos = pos; + } + /** * Copy constructor. */ @@ -341,15 +393,6 @@ public class ColorBand { this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f); } - @Override - public ColorBandData clone() { - try { - return (ColorBandData) super.clone(); - } catch (CloneNotSupportedException e) { - return new ColorBandData(this); - } - } - @Override public String toString() { return "P: " + this.pos + " [" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + "]"; diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java index fac8ca0e7..d7e62e680 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java @@ -3,13 +3,16 @@ package com.jme3.scene.plugins.blender.textures; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; import jme3tools.converters.ImageToAwt; +import com.jme3.math.ColorRGBA; import com.jme3.math.Vector2f; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; @@ -30,6 +33,8 @@ import com.jme3.texture.Texture.MagFilter; import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.Texture.WrapMode; import com.jme3.texture.Texture2D; +import com.jme3.texture.TextureCubeMap; +import com.jme3.util.BufferUtils; /** * This class represents a texture that is defined for the material. It can be @@ -43,6 +48,11 @@ public class CombinedTexture { /** The mapping type of the texture. Defined bu MaterialContext.MTEX_COL, MTEX_NOR etc. */ private final int mappingType; + /** + * If set to true then if a texture without alpha is added then all textures below are discarded because + * the new one will cover them anyway. If set to false then all textures are stored. + */ + private boolean discardCoveredTextures; /** The data for each of the textures. */ private List textureDatas = new ArrayList(); /** The result texture. */ @@ -55,9 +65,13 @@ public class CombinedTexture { * * @param mappingType * texture mapping type + * @param discardCoveredTextures + * if set to true then if a texture without alpha is added then all textures below are discarded because + * the new one will cover them anyway, if set to false then all textures are stored */ - public CombinedTexture(int mappingType) { + public CombinedTexture(int mappingType, boolean discardCoveredTextures) { this.mappingType = mappingType; + this.discardCoveredTextures = discardCoveredTextures; } /** @@ -75,7 +89,7 @@ public class CombinedTexture { * @param textureStructure * the texture sructure * @param uvCoordinatesName - * the name of the used user's UV coordinates for this texture + * the name of the used user's UV coordinates for this texture * @param blenderContext * the blender context */ @@ -93,7 +107,7 @@ public class CombinedTexture { textureData.textureStructure = textureStructure; textureData.uvCoordinatesName = uvCoordinatesName; - if (textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) { + if (discardCoveredTextures && textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) { textureDatas.clear();// clear previous textures, they will be covered anyway } textureDatas.add(textureData); @@ -120,7 +134,6 @@ public class CombinedTexture { */ @SuppressWarnings("unchecked") public void flatten(Geometry geometry, Long geometriesOMA, LinkedHashMap> userDefinedUVCoordinates, BlenderContext blenderContext) { - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); Mesh mesh = geometry.getMesh(); Texture previousTexture = null; UVCoordinatesType masterUVCoordinatesType = null; @@ -128,7 +141,7 @@ public class CombinedTexture { for (TextureData textureData : textureDatas) { // decompress compressed textures (all will be merged into one texture anyway) if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) { - textureData.texture.setImage(textureHelper.decompress(textureData.texture.getImage())); + textureData.texture.setImage(ImageUtils.decompress(textureData.texture.getImage())); textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender); } @@ -139,8 +152,8 @@ public class CombinedTexture { resultTexture = textureData.texture; if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { - if(textureData.uvCoordinatesName == null) { - resultUVS = userDefinedUVCoordinates.values().iterator().next();//get the first UV available + if (textureData.uvCoordinatesName == null) { + resultUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available } else { resultUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName); } @@ -167,11 +180,9 @@ public class CombinedTexture { triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext); resultTexture = previousTexture = triangulatedTexture; } else if (textureData.texture instanceof Texture2D) { - if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName, - textureData.uvCoordinatesType, textureData.uvCoordinatesName) && - resultTexture instanceof Texture2D) { + if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName, textureData.uvCoordinatesType, textureData.uvCoordinatesName) && resultTexture instanceof Texture2D) { this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight()); - this.merge((Texture2D) resultTexture, (Texture2D) textureData.texture); + ImageUtils.merge(resultTexture.getImage(), textureData.texture.getImage()); previousTexture = resultTexture; } else { if (!(resultTexture instanceof TriangulatedTexture)) { @@ -181,8 +192,8 @@ public class CombinedTexture { // first triangulate the current texture List textureUVS = null; if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { - if(textureData.uvCoordinatesName == null) { - textureUVS = userDefinedUVCoordinates.values().iterator().next();//get the first UV available + if (textureData.uvCoordinatesName == null) { + textureUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available } else { textureUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName); } @@ -193,7 +204,10 @@ public class CombinedTexture { TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext); // then move the texture to different UV's triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext); - ((TriangulatedTexture) resultTexture).merge(triangulatedTexture); + // merge triangulated textures + for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) { + ImageUtils.merge(((TriangulatedTexture) resultTexture).getFaceTextureElement(i).image, triangulatedTexture.getImage()); + } } } } @@ -203,7 +217,7 @@ public class CombinedTexture { if (mappingType == MaterialContext.MTEX_NOR) { for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) { TriangleTextureElement triangleTextureElement = ((TriangulatedTexture) resultTexture).getFaceTextureElement(i); - triangleTextureElement.image = textureHelper.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor + triangleTextureElement.image = ImageUtils.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor } } resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS(); @@ -218,22 +232,102 @@ public class CombinedTexture { resultTexture.setMinFilter(MinFilter.NearestNoMipMaps); } + /** + * Generates a texture that will be used by the sky spatial. + * The result texture has 6 layers. Every image in each layer has equal size and its shape is a square. + * The size of each image is the maximum size (width or height) of the textures given. + * The default sky generated texture size is used (this value is set in the BlenderKey) if no picture textures + * are present or their sizes is lower than the generated texture size. + * The textures of lower sizes are properly scaled. + * All the textures are mixed into one and put as layers in the result texture. + * + * @param horizontalColor + * the horizon color + * @param zenithColor + * the zenith color + * @param blenderContext + * the blender context + * @return texture for the sky + */ + public TextureCubeMap generateSkyTexture(ColorRGBA horizontalColor, ColorRGBA zenithColor, BlenderContext blenderContext) { + LOGGER.log(Level.FINE, "Preparing sky texture from {0} applied textures.", textureDatas.size()); + + LOGGER.fine("Computing the texture size."); + int size = -1; + for (TextureData textureData : textureDatas) { + if (textureData.texture instanceof Texture2D) { + size = Math.max(textureData.texture.getImage().getWidth(), size); + size = Math.max(textureData.texture.getImage().getHeight(), size); + } + } + if (size < 0) { + size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize(); + } + LOGGER.log(Level.FINE, "The sky texture size will be: {0}x{0}.", size); + + TextureCubeMap result = null; + for (TextureData textureData : textureDatas) { + TextureCubeMap texture = null; + if (textureData.texture instanceof GeneratedTexture) { + texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor); + } else { + // first create a grayscale version of the image + Image image = textureData.texture.getImage(); + if (image.getWidth() != image.getHeight() || image.getWidth() != size) { + image = ImageUtils.resizeTo(image, size, size); + } + Image grayscaleImage = ImageUtils.convertToGrayscaleTexture(image); + + // add the sky colors to the image + PixelInputOutput sourcePixelIO = PixelIOFactory.getPixelIO(grayscaleImage.getFormat()); + PixelInputOutput targetPixelIO = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel texturePixel = new TexturePixel(); + for (int x = 0; x < image.getWidth(); ++x) { + for (int y = 0; y < image.getHeight(); ++y) { + sourcePixelIO.read(grayscaleImage, 0, texturePixel, x, y); + texturePixel.intensity = texturePixel.red;// no matter which factor we use here, in grayscale they are all equal + ImageUtils.color(texturePixel, horizontalColor, zenithColor); + targetPixelIO.write(image, 0, texturePixel, x, y); + } + } + + // create the cubemap texture from the coloured image + ByteBuffer sourceData = image.getData(0); + ArrayList data = new ArrayList(6); + for (int i = 0; i < 6; ++i) { + data.add(BufferUtils.clone(sourceData)); + } + texture = new TextureCubeMap(new Image(image.getFormat(), image.getWidth(), image.getHeight(), 6, data)); + } + + if (result == null) { + result = texture; + } else { + ImageUtils.mix(result.getImage(), texture.getImage()); + } + } + return result; + } + /** * The method checks if the texture UV coordinates match. * It the types are equal and different then UVCoordinatesType.TEXCO_UV then we consider them a match. * If they are both UVCoordinatesType.TEXCO_UV then they match only when their UV sets names are equal. * In other cases they are considered NOT a match. - * @param type1 the UV coord type - * @param uvSetName1 the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV) - * @param type2 the UV coord type - * @param uvSetName2 the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV) + * @param type1 + * the UV coord type + * @param uvSetName1 + * the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV) + * @param type2 + * the UV coord type + * @param uvSetName2 + * the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV) * @return true if the types match and false otherwise */ - private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1, - UVCoordinatesType type2, String uvSetName2) { - if(type1 == type2) { - if(type1 == UVCoordinatesType.TEXCO_UV) { - if(uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) { + private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1, UVCoordinatesType type2, String uvSetName2) { + if (type1 == type2) { + if (type1 == UVCoordinatesType.TEXCO_UV) { + if (uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) { return true; } } else { @@ -242,7 +336,7 @@ public class CombinedTexture { } return false; } - + /** * This method blends the texture. * @@ -298,40 +392,7 @@ public class CombinedTexture { } return false; } - - /** - * This method merges two given textures. The result is stored in the - * 'target' texture. - * - * @param target - * the target texture - * @param source - * the source texture - */ - private void merge(Texture2D target, Texture2D source) { - if (target.getImage().getDepth() != source.getImage().getDepth()) { - throw new IllegalArgumentException("Cannot merge images with different depths!"); - } - Image sourceImage = source.getImage(); - Image targetImage = target.getImage(); - PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); - PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); - TexturePixel sourcePixel = new TexturePixel(); - TexturePixel targetPixel = new TexturePixel(); - int depth = target.getImage().getDepth() == 0 ? 1 : target.getImage().getDepth(); - - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < sourceImage.getWidth(); ++x) { - for (int y = 0; y < sourceImage.getHeight(); ++y) { - sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y); - targetIO.read(targetImage, layerIndex, targetPixel, x, y); - targetPixel.merge(sourcePixel); - targetIO.write(targetImage, layerIndex, targetPixel, x, y); - } - } - } - } - + /** * This method determines if the given texture has no alpha channel. * @@ -460,6 +521,6 @@ public class CombinedTexture { /** The texture sructure. */ public Structure textureStructure; /** The name of the user's UV coordinates that are used for this texture. */ - public String uvCoordinatesName; + public String uvCoordinatesName; } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java index f36666a2f..35c821945 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java @@ -1,6 +1,13 @@ package com.jme3.scene.plugins.blender.textures; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + import com.jme3.bounding.BoundingBox; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; @@ -10,12 +17,12 @@ import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement; import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType; import com.jme3.scene.plugins.blender.textures.generating.TextureGenerator; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; +import com.jme3.texture.TextureCubeMap; /** * The generated texture loaded from blender file. The texture is not generated @@ -25,6 +32,13 @@ import java.util.TreeSet; * @author Marcin Roguski (Kaelthas) */ /* package */class GeneratedTexture extends Texture { + private static final int POSITIVE_X = 0; + private static final int NEGATIVE_X = 1; + // private static final int POSITIVE_Y = 2; + private static final int NEGATIVE_Y = 3; + private static final int POSITIVE_Z = 4; + private static final int NEGATIVE_Z = 5; + // flag values public static final int TEX_COLORBAND = 1; public static final int TEX_FLIPBLEND = 2; @@ -115,6 +129,61 @@ import java.util.TreeSet; return new TriangulatedTexture(triangleTextureElements, blenderContext); } + /** + * Creates a texture for the sky. The result texture has 6 layers. + * @param size + * the size of the texture (width and height are equal) + * @param horizontalColor + * the horizon color + * @param zenithColor + * the zenith color + * @return the sky texture + */ + public TextureCubeMap generateSkyTexture(int size, ColorRGBA horizontalColor, ColorRGBA zenithColor) { + Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6); + PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel pixel = new TexturePixel(); + + float delta = 1 / (float) (size - 1); + float sideV, sideS = 1, forwardU = 1, forwardV, upS; + + long time = System.currentTimeMillis(); + for (int x = 0; x < size; ++x) { + sideV = 1; + forwardV = 1; + upS = 0; + for (int y = 0; y < size; ++y) { + textureGenerator.getPixel(pixel, 1, sideV, sideS); + pixelIO.write(image, NEGATIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// right + + textureGenerator.getPixel(pixel, 0, sideV, 1 - sideS); + pixelIO.write(image, POSITIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// left + + textureGenerator.getPixel(pixel, forwardU, forwardV, 0); + pixelIO.write(image, POSITIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// front + + textureGenerator.getPixel(pixel, 1 - forwardU, forwardV, 1); + pixelIO.write(image, NEGATIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// back + + textureGenerator.getPixel(pixel, forwardU, 0, upS); + pixelIO.write(image, NEGATIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// top + + // textureGenerator.getPixel(pixel, forwardU, 1, upS); + // pixelIO.write(image, POSITIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);//bottom + + sideV = FastMath.clamp(sideV - delta, 0, 1); + forwardV = FastMath.clamp(forwardV - delta, 0, 1); + upS = FastMath.clamp(upS + delta, 0, 1); + } + sideS = FastMath.clamp(sideS - delta, 0, 1); + forwardU = FastMath.clamp(forwardU - delta, 0, 1); + } + + System.out.println(System.currentTimeMillis() - time); + + return new TextureCubeMap(image); + } + @Override public void setWrap(WrapAxis axis, WrapMode mode) { } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageUtils.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageUtils.java new file mode 100644 index 000000000..40755f83e --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageUtils.java @@ -0,0 +1,471 @@ +package com.jme3.scene.plugins.blender.textures; + +import java.awt.color.ColorSpace; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.ColorConvertOp; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import jme3tools.converters.ImageToAwt; +import jme3tools.converters.RGB565; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; + +/** + * This utility class has the methods that deal with images. + * + * @author Marcin Roguski (Kaelthas) + */ +public final class ImageUtils { + /** + * Creates an image of the given size and depth. + * @param format + * the image format + * @param width + * the image width + * @param height + * the image height + * @param depth + * the image depth + * @return the new image instance + */ + public static Image createEmptyImage(Format format, int width, int height, int depth) { + int bufferSize = width * height * (format.getBitsPerPixel() >> 3); + if (depth < 2) { + return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize)); + } + ArrayList data = new ArrayList(depth); + for (int i = 0; i < depth; ++i) { + data.add(BufferUtils.createByteBuffer(bufferSize)); + } + return new Image(Format.RGB8, width, height, depth, data); + } + + /** + * The method sets a color for the given pixel by merging the two given colors. + * The lowIntensityColor will be most visible when the pixel has low intensity. + * The highIntensityColor will be most visible when the pixel has high intensity. + * + * @param pixel + * the pixel that will have the colors altered + * @param lowIntensityColor + * the low intensity color + * @param highIntensityColor + * the high intensity color + * @return the altered pixel (the same instance) + */ + public static TexturePixel color(TexturePixel pixel, ColorRGBA lowIntensityColor, ColorRGBA highIntensityColor) { + float intensity = pixel.intensity; + pixel.fromColor(lowIntensityColor); + pixel.mult(1 - pixel.intensity); + pixel.add(highIntensityColor.mult(intensity)); + return pixel; + } + + /** + * This method merges two given images. The result is stored in the + * 'target' image. + * + * @param targetImage + * the target image + * @param sourceImage + * the source image + */ + public static void merge(Image targetImage, Image sourceImage) { + if (sourceImage.getDepth() != targetImage.getDepth()) { + throw new IllegalArgumentException("The given images should have the same depth to merge them!"); + } + if (sourceImage.getWidth() != targetImage.getWidth()) { + throw new IllegalArgumentException("The given images should have the same width to merge them!"); + } + if (sourceImage.getHeight() != targetImage.getHeight()) { + throw new IllegalArgumentException("The given images should have the same height to merge them!"); + } + + PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); + PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); + TexturePixel sourcePixel = new TexturePixel(); + TexturePixel targetPixel = new TexturePixel(); + int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth(); + + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < sourceImage.getWidth(); ++x) { + for (int y = 0; y < sourceImage.getHeight(); ++y) { + sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y); + targetIO.read(targetImage, layerIndex, targetPixel, x, y); + targetPixel.merge(sourcePixel); + targetIO.write(targetImage, layerIndex, targetPixel, x, y); + } + } + } + } + + /** + * This method merges two given images. The result is stored in the + * 'target' image. + * + * @param targetImage + * the target image + * @param sourceImage + * the source image + */ + public static void mix(Image targetImage, Image sourceImage) { + if (sourceImage.getDepth() != targetImage.getDepth()) { + throw new IllegalArgumentException("The given images should have the same depth to merge them!"); + } + if (sourceImage.getWidth() != targetImage.getWidth()) { + throw new IllegalArgumentException("The given images should have the same width to merge them!"); + } + if (sourceImage.getHeight() != targetImage.getHeight()) { + throw new IllegalArgumentException("The given images should have the same height to merge them!"); + } + + PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); + PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); + TexturePixel sourcePixel = new TexturePixel(); + TexturePixel targetPixel = new TexturePixel(); + int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth(); + + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < sourceImage.getWidth(); ++x) { + for (int y = 0; y < sourceImage.getHeight(); ++y) { + sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y); + targetIO.read(targetImage, layerIndex, targetPixel, x, y); + targetPixel.mix(sourcePixel); + targetIO.write(targetImage, layerIndex, targetPixel, x, y); + } + } + } + } + + /** + * Resizes the image to the given width and height. + * @param source + * the source image (this remains untouched, the new image instance is created) + * @param width + * the target image width + * @param height + * the target image height + * @return the resized image + */ + public static Image resizeTo(Image source, int width, int height) { + BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); + + double scaleX = width / (double) sourceImage.getWidth(); + double scaleY = height / (double) sourceImage.getHeight(); + AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY); + AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR); + + BufferedImage scaledImage = bilinearScaleOp.filter(sourceImage, new BufferedImage(width, height, sourceImage.getType())); + return ImageUtils.toJmeImage(scaledImage, source.getFormat()); + } + + /** + * This method converts the given texture into normal-map texture. + * + * @param source + * the source texture + * @param strengthFactor + * the normal strength factor + * @return normal-map texture + */ + public static Image convertToNormalMapTexture(Image source, float strengthFactor) { + BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); + + BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); + BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); + ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); + gscale.filter(sourceImage, heightMap); + + Vector3f S = new Vector3f(); + Vector3f T = new Vector3f(); + Vector3f N = new Vector3f(); + + for (int x = 0; x < bumpMap.getWidth(); ++x) { + for (int y = 0; y < bumpMap.getHeight(); ++y) { + // generating bump pixel + S.x = 1; + S.y = 0; + S.z = strengthFactor * ImageUtils.getHeight(heightMap, x + 1, y) - strengthFactor * ImageUtils.getHeight(heightMap, x - 1, y); + T.x = 0; + T.y = 1; + T.z = strengthFactor * ImageUtils.getHeight(heightMap, x, y + 1) - strengthFactor * ImageUtils.getHeight(heightMap, x, y - 1); + + float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1); + N.x = -S.z; + N.y = -T.z; + N.z = 1; + N.divideLocal(den); + + // setting thge pixel in the result image + bumpMap.setRGB(x, y, ImageUtils.vectorToColor(N.x, N.y, N.z)); + } + } + return ImageUtils.toJmeImage(bumpMap, source.getFormat()); + } + + /** + * This method converts the given texture into black and whit (grayscale) texture. + * + * @param source + * the source texture + * @return grayscale texture + */ + public static Image convertToGrayscaleTexture(Image source) { + BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); + ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); + op.filter(sourceImage, sourceImage); + return ImageUtils.toJmeImage(sourceImage, source.getFormat()); + } + + /** + * This method decompresses the given image. If the given image is already + * decompressed nothing happens and it is simply returned. + * + * @param image + * the image to decompress + * @return the decompressed image + */ + public static Image decompress(Image image) { + Format format = image.getFormat(); + int depth = image.getDepth(); + if (depth == 0) { + depth = 1; + } + ArrayList dataArray = new ArrayList(depth); + int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1]; + int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null; + + for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { + ByteBuffer data = image.getData(dataLayerIndex); + data.rewind(); + if (sizes.length == 1) { + sizes[0] = data.remaining(); + } + float widthToHeightRatio = image.getWidth() / image.getHeight();// this should always be constant for each mipmap + List texelDataList = new ArrayList(sizes.length); + int maxPosition = 0, resultSize = 0; + + for (int sizeIndex = 0; sizeIndex < sizes.length; ++sizeIndex) { + maxPosition += sizes[sizeIndex]; + DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format); + texelDataList.add(texelData); + switch (format) { + case DXT1:// BC1 + case DXT1A: + while (data.position() < maxPosition) { + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + if (col0 > col1) { + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + } else { + // creating color2 = 1/2color0 + 1/2color1 + colors[2].fromPixel(colors[0]); + colors[2].add(colors[1]); + colors[2].mult(0.5f); + + colors[3].fromARGB8(0); + } + int indexes = data.getInt();// 4-byte table with color indexes in decompressed table + texelData.add(colors, indexes); + } + break; + case DXT3:// BC2 + while (data.position() < maxPosition) { + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + long alpha = data.getLong(); + float[] alphas = new float[16]; + long alphasIndex = 0; + for (int i = 0; i < 16; ++i) { + alphasIndex |= i << i * 4; + byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); + alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + int indexes = data.getInt();// 4-byte table with color indexes in decompressed table + texelData.add(colors, indexes, alphas, alphasIndex); + } + break; + case DXT5:// BC3 + float[] alphas = new float[8]; + while (data.position() < maxPosition) { + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + alphas[0] = data.get() * 255.0f; + alphas[1] = data.get() * 255.0f; + long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40; + if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. + alphas[2] = (6 * alphas[0] + alphas[1]) / 7; + alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; + alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; + alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; + alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; + alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; + } else { + alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; + alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; + alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; + alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; + alphas[6] = 0; + alphas[7] = 1; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + int indexes = data.getInt();// 4-byte table with color indexes in decompressed table + texelData.add(colors, indexes, alphas, alphaIndices); + } + break; + default: + throw new IllegalStateException("Unknown compressed format: " + format); + } + newMipmapSizes[sizeIndex] = texelData.getSizeInBytes(); + resultSize += texelData.getSizeInBytes(); + } + byte[] bytes = new byte[resultSize]; + int offset = 0; + byte[] pixelBytes = new byte[4]; + for (DDSTexelData texelData : texelDataList) { + for (int i = 0; i < texelData.getPixelWidth(); ++i) { + for (int j = 0; j < texelData.getPixelHeight(); ++j) { + if (texelData.getRGBA8(i, j, pixelBytes)) { + bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0]; + bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1]; + bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2]; + bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3]; + } else { + break; + } + } + } + offset += texelData.getSizeInBytes(); + } + dataArray.add(BufferUtils.createByteBuffer(bytes)); + } + + Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0)); + if (newMipmapSizes != null) { + result.setMipMapSizes(newMipmapSizes); + } + return result; + } + + /** + * This method returns the height represented by the specified pixel in the + * given texture. The given texture should be a height-map. + * + * @param image + * the height-map texture + * @param x + * pixel's X coordinate + * @param y + * pixel's Y coordinate + * @return height reprezented by the given texture in the specified location + */ + private static int getHeight(BufferedImage image, int x, int y) { + if (x < 0) { + x = 0; + } else if (x >= image.getWidth()) { + x = image.getWidth() - 1; + } + if (y < 0) { + y = 0; + } else if (y >= image.getHeight()) { + y = image.getHeight() - 1; + } + return image.getRGB(x, y) & 0xff; + } + + /** + * This method transforms given vector's coordinates into ARGB color (A is + * always = 255). + * + * @param x + * X factor of the vector + * @param y + * Y factor of the vector + * @param z + * Z factor of the vector + * @return color representation of the given vector + */ + private static int vectorToColor(float x, float y, float z) { + int r = Math.round(255 * (x + 1f) / 2f); + int g = Math.round(255 * (y + 1f) / 2f); + int b = Math.round(255 * (z + 1f) / 2f); + return (255 << 24) + (r << 16) + (g << 8) + b; + } + + /** + * Converts java awt image to jme image. + * @param bufferedImage + * the java awt image + * @param format + * the result image format + * @return the jme image + */ + private static Image toJmeImage(BufferedImage bufferedImage, Format format) { + ByteBuffer byteBuffer = BufferUtils.createByteBuffer(bufferedImage.getWidth() * bufferedImage.getHeight() * 3); + ImageToAwt.convert(bufferedImage, format, byteBuffer); + return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer); + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java index 22cfca322..5ec300e68 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java @@ -31,21 +31,17 @@ */ package com.jme3.scene.plugins.blender.textures; -import com.jme3.asset.AssetInfo; - -import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.awt.image.ColorConvertOp; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; -import jme3tools.converters.ImageToAwt; -import jme3tools.converters.RGB565; - +import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetManager; import com.jme3.asset.AssetNotFoundException; import com.jme3.asset.BlenderKey; @@ -53,20 +49,23 @@ import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.asset.GeneratedTextureKey; import com.jme3.asset.TextureKey; import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; +import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType; +import com.jme3.scene.plugins.blender.textures.blending.TextureBlender; +import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory; import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorFactory; import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.Texture.WrapMode; @@ -205,280 +204,6 @@ public class TextureHelper extends AbstractBlenderHelper { return result; } - /** - * This method converts the given texture into normal-map texture. - * - * @param source - * the source texture - * @param strengthFactor - * the normal strength factor - * @return normal-map texture - */ - public Image convertToNormalMapTexture(Image source, float strengthFactor) { - BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); - - BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); - BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); - ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); - gscale.filter(sourceImage, heightMap); - - Vector3f S = new Vector3f(); - Vector3f T = new Vector3f(); - Vector3f N = new Vector3f(); - - for (int x = 0; x < bumpMap.getWidth(); ++x) { - for (int y = 0; y < bumpMap.getHeight(); ++y) { - // generating bump pixel - S.x = 1; - S.y = 0; - S.z = strengthFactor * this.getHeight(heightMap, x + 1, y) - strengthFactor * this.getHeight(heightMap, x - 1, y); - T.x = 0; - T.y = 1; - T.z = strengthFactor * this.getHeight(heightMap, x, y + 1) - strengthFactor * this.getHeight(heightMap, x, y - 1); - - float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1); - N.x = -S.z; - N.y = -T.z; - N.z = 1; - N.divideLocal(den); - - // setting thge pixel in the result image - bumpMap.setRGB(x, y, this.vectorToColor(N.x, N.y, N.z)); - } - } - ByteBuffer byteBuffer = BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 3); - ImageToAwt.convert(bumpMap, Format.RGB8, byteBuffer); - return new Image(Format.RGB8, source.getWidth(), source.getHeight(), byteBuffer); - } - - /** - * This method decompresses the given image. If the given image is already - * decompressed nothing happens and it is simply returned. - * - * @param image - * the image to decompress - * @return the decompressed image - */ - public Image decompress(Image image) { - Format format = image.getFormat(); - int depth = image.getDepth(); - if (depth == 0) { - depth = 1; - } - ArrayList dataArray = new ArrayList(depth); - int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1]; - int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null; - - for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { - ByteBuffer data = image.getData(dataLayerIndex); - data.rewind(); - if (sizes.length == 1) { - sizes[0] = data.remaining(); - } - float widthToHeightRatio = image.getWidth() / image.getHeight();// this should always be constant for each mipmap - List texelDataList = new ArrayList(sizes.length); - int maxPosition = 0, resultSize = 0; - - for (int sizeIndex = 0; sizeIndex < sizes.length; ++sizeIndex) { - maxPosition += sizes[sizeIndex]; - DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format); - texelDataList.add(texelData); - switch (format) { - case DXT1:// BC1 - case DXT1A: - while (data.position() < maxPosition) { - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - if (col0 > col1) { - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - } else { - // creating color2 = 1/2color0 + 1/2color1 - colors[2].fromPixel(colors[0]); - colors[2].add(colors[1]); - colors[2].mult(0.5f); - - colors[3].fromARGB8(0); - } - int indexes = data.getInt();// 4-byte table with color indexes in decompressed table - texelData.add(colors, indexes); - } - break; - case DXT3:// BC2 - while (data.position() < maxPosition) { - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - long alpha = data.getLong(); - float[] alphas = new float[16]; - long alphasIndex = 0; - for (int i = 0; i < 16; ++i) { - alphasIndex |= i << i * 4; - byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); - alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - int indexes = data.getInt();// 4-byte table with color indexes in decompressed table - texelData.add(colors, indexes, alphas, alphasIndex); - } - break; - case DXT5:// BC3 - float[] alphas = new float[8]; - while (data.position() < maxPosition) { - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - alphas[0] = data.get() * 255.0f; - alphas[1] = data.get() * 255.0f; - long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40; - if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. - alphas[2] = (6 * alphas[0] + alphas[1]) / 7; - alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; - alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; - alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; - alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; - alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; - } else { - alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; - alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; - alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; - alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; - alphas[6] = 0; - alphas[7] = 1; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - int indexes = data.getInt();// 4-byte table with color indexes in decompressed table - texelData.add(colors, indexes, alphas, alphaIndices); - } - break; - default: - throw new IllegalStateException("Unknown compressed format: " + format); - } - newMipmapSizes[sizeIndex] = texelData.getSizeInBytes(); - resultSize += texelData.getSizeInBytes(); - } - byte[] bytes = new byte[resultSize]; - int offset = 0; - byte[] pixelBytes = new byte[4]; - for (DDSTexelData texelData : texelDataList) { - for (int i = 0; i < texelData.getPixelWidth(); ++i) { - for (int j = 0; j < texelData.getPixelHeight(); ++j) { - if (texelData.getRGBA8(i, j, pixelBytes)) { - bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0]; - bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1]; - bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2]; - bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3]; - } else { - break; - } - } - } - offset += texelData.getSizeInBytes(); - } - dataArray.add(BufferUtils.createByteBuffer(bytes)); - } - - Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0)); - if (newMipmapSizes != null) { - result.setMipMapSizes(newMipmapSizes); - } - return result; - } - - /** - * This method returns the height represented by the specified pixel in the - * given texture. The given texture should be a height-map. - * - * @param image - * the height-map texture - * @param x - * pixel's X coordinate - * @param y - * pixel's Y coordinate - * @return height reprezented by the given texture in the specified location - */ - protected int getHeight(BufferedImage image, int x, int y) { - if (x < 0) { - x = 0; - } else if (x >= image.getWidth()) { - x = image.getWidth() - 1; - } - if (y < 0) { - y = 0; - } else if (y >= image.getHeight()) { - y = image.getHeight() - 1; - } - return image.getRGB(x, y) & 0xff; - } - - /** - * This method transforms given vector's coordinates into ARGB color (A is - * always = 255). - * - * @param x - * X factor of the vector - * @param y - * Y factor of the vector - * @param z - * Z factor of the vector - * @return color representation of the given vector - */ - protected int vectorToColor(float x, float y, float z) { - int r = Math.round(255 * (x + 1f) / 2f); - int g = Math.round(255 * (y + 1f) / 2f); - int b = Math.round(255 * (z + 1f) / 2f); - return (255 << 24) + (r << 16) + (g << 8) + b; - } - /** * This class returns a texture read from the file or from packed blender * data. @@ -797,9 +522,96 @@ public class TextureHelper extends AbstractBlenderHelper { return result; } + + @SuppressWarnings("unchecked") + public Map readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException { + DynamicArray mtexsArray = (DynamicArray) structure.getFieldValue("mtex"); + int separatedTextures = skyTexture ? 0 : ((Number) structure.getFieldValue("septex")).intValue(); + List texturesList = new ArrayList(); + for (int i = 0; i < mtexsArray.getTotalSize(); ++i) { + Pointer p = mtexsArray.get(i); + if (p.isNotNull() && (separatedTextures & 1 << i) == 0) { + TextureData textureData = new TextureData(); + textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0); + textureData.uvCoordinatesType = skyTexture ? UVCoordinatesType.TEXCO_ORCO.blenderValue : ((Number) textureData.mtex.getFieldValue("texco")).intValue(); + textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue(); + textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString(); + if(textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) { + textureData.uvCoordinatesName = null; + } + + Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex"); + if (pTex.isNotNull()) { + Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0); + textureData.textureStructure = tex; + texturesList.add(textureData); + } + } + } + + // loading the textures and merging them + Map> textureDataMap = this.sortTextures(texturesList); + Map loadedTextures = new HashMap(); + for (Entry> entry : textureDataMap.entrySet()) { + if (entry.getValue().size() > 0) { + CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue(), !skyTexture); + for (TextureData textureData : entry.getValue()) { + int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); + boolean negateTexture = (texflag & 0x04) != 0; + Texture texture = this.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); + if (texture != null) { + int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); + float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; + float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); + TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac); + combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, + textureData.textureStructure, textureData.uvCoordinatesName, blenderContext); + } + } + if (combinedTexture.getTexturesCount() > 0) { + loadedTextures.put(entry.getKey(), combinedTexture); + } + } + } + return loadedTextures; + } + + /** + * This method sorts the textures by their mapping type. In each group only + * textures of one type are put (either two- or three-dimensional). + * + * @return a map with sorted textures + */ + private Map> sortTextures(List textures) { + int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB }; + Map> result = new HashMap>(); + for (TextureData data : textures) { + Number mapto = (Number) data.mtex.getFieldValue("mapto"); + for (int i = 0; i < mappings.length; ++i) { + if ((mappings[i] & mapto.intValue()) != 0) { + List datas = result.get(mappings[i]); + if (datas == null) { + datas = new ArrayList(); + result.put(mappings[i], datas); + } + datas.add(data); + } + } + } + return result; + } @Override public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0; } + + public static class TextureData { + public Structure mtex; + public Structure textureStructure; + public int uvCoordinatesType; + public int projectionType; + /** The name of the user's UV coordinates that are used for this texture. */ + public String uvCoordinatesName; + } } \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java index 9e7c5b4ff..365b34f5e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java @@ -275,6 +275,20 @@ public class TexturePixel implements Cloneable { this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue; this.alpha = (this.alpha + pixel.alpha) * 0.5f; } + + /** + * Mixes two pixels. + * + * @param pixel + * the pixel we mix with + */ + public void mix(TexturePixel pixel) { + this.red = 0.5f * (this.red + pixel.red); + this.green = 0.5f * (this.green + pixel.green); + this.blue = 0.5f * (this.blue + pixel.blue); + this.alpha = 0.5f * (this.alpha + pixel.alpha); + this.intensity = 0.5f * (this.intensity + pixel.intensity); + } /** * This method negates the colors. @@ -306,6 +320,19 @@ public class TexturePixel implements Cloneable { this.alpha += pixel.alpha; this.intensity += pixel.intensity; } + + /** + * This method adds the calues of the given pixel to the current pixel. + * + * @param pixel + * the pixel we add + */ + public void add(ColorRGBA pixel) { + this.red += pixel.r; + this.green += pixel.g; + this.blue += pixel.b; + this.alpha += pixel.a; + } /** * This method multiplies the values of the given pixel by the given value. diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java index 4fa34248e..fd4044244 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java @@ -168,33 +168,6 @@ import com.jme3.util.BufferUtils; } } - /** - * This method merges the current texture with the given one. The given - * texture is not changed. - * - * @param triangulatedTexture - * the texture we merge current texture with - */ - public void merge(TriangulatedTexture triangulatedTexture) { - TexturePixel sourcePixel = new TexturePixel(); - TexturePixel targetPixel = new TexturePixel(); - for (TriangleTextureElement triangleTextureElement : this.faceTextures) { - Image sourceImage = triangulatedTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image; - Image targetImage = triangleTextureElement.image; - PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); - PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); - - for (int x = 0; x < sourceImage.getWidth(); ++x) { - for (int y = 0; y < sourceImage.getHeight(); ++y) { - sourceIO.read(sourceImage, 0, sourcePixel, x, y); - targetIO.read(targetImage, 0, targetPixel, x, y); - targetPixel.merge(sourcePixel); - targetIO.write(targetImage, 0, targetPixel, x, y); - } - } - } - } - /** * This method returns the flat texture. It is calculated if required or if * it was not created before. Images that are identical are discarded to