/* * Copyright (c) 2009-2012 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 java.awt.geom.AffineTransform; 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 com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoadException; import com.jme3.asset.AssetManager; import com.jme3.asset.AssetNotFoundException; import com.jme3.asset.BlenderKey; import com.jme3.asset.GeneratedTextureKey; import com.jme3.asset.TextureKey; import com.jme3.math.Vector2f; 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.LoadedDataType; 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.Texture; import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.Texture.WrapMode; import com.jme3.texture.Texture2D; import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; /** * A class that is used in texture calculations. * * @author Marcin Roguski */ public class TextureHelper extends AbstractBlenderHelper { private static final Logger LOGGER = Logger.getLogger(TextureHelper.class.getName()); // texture types public static final int TEX_NONE = 0; public static final int TEX_CLOUDS = 1; public static final int TEX_WOOD = 2; public static final int TEX_MARBLE = 3; public static final int TEX_MAGIC = 4; public static final int TEX_BLEND = 5; public static final int TEX_STUCCI = 6; public static final int TEX_NOISE = 7; public static final int TEX_IMAGE = 8; public static final int TEX_PLUGIN = 9; public static final int TEX_ENVMAP = 10; public static final int TEX_MUSGRAVE = 11; public static final int TEX_VORONOI = 12; public static final int TEX_DISTNOISE = 13; public static final int TEX_POINTDENSITY = 14; // v. 25+ public static final int TEX_VOXELDATA = 15; // v. 25+ public static final int TEX_OCEAN = 16; // v. 26+ public static final Type[] TEXCOORD_TYPES = new Type[] { Type.TexCoord, Type.TexCoord2, Type.TexCoord3, Type.TexCoord4, Type.TexCoord5, Type.TexCoord6, Type.TexCoord7, Type.TexCoord8 }; private TextureGeneratorFactory textureGeneratorFactory = new TextureGeneratorFactory(); /** * This constructor parses the given blender version and stores the result. * It creates noise generator and texture generators. * * @param blenderVersion * the version read from the blend file * @param blenderContext * the blender context */ public TextureHelper(String blenderVersion, BlenderContext blenderContext) { super(blenderVersion, blenderContext); } /** * 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 its blender * type. * * @param textureStructure * texture structure filled with data * @param blenderContext * the blender context * @return the texture that can be used by JME engine * @throws BlenderFileException * this exception is thrown when the blend file structure is * somehow invalid or corrupted */ public Texture getTexture(Structure textureStructure, Structure mTex, BlenderContext blenderContext) throws BlenderFileException { Texture result = (Texture) blenderContext.getLoadedFeature(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); if (result != null) { return result; } if ("ID".equals(textureStructure.getType())) { LOGGER.fine("Loading texture from external blend file."); return (Texture) this.loadLibrary(textureStructure); } int type = ((Number) textureStructure.getFieldValue("type")).intValue(); int imaflag = ((Number) textureStructure.getFieldValue("imaflag")).intValue(); switch (type) { case TEX_IMAGE:// (it is first because probably this will be most commonly used) Pointer pImage = (Pointer) textureStructure.getFieldValue("ima"); if (pImage.isNotNull()) { Structure image = pImage.fetchData().get(0); Texture loadedTexture = this.loadImageAsTexture(image, imaflag, blenderContext); if (loadedTexture != null) { result = loadedTexture; this.applyColorbandAndColorFactors(textureStructure, result.getImage(), blenderContext); } } break; case TEX_CLOUDS: case TEX_WOOD: case TEX_MARBLE: case TEX_MAGIC: case TEX_BLEND: case TEX_STUCCI: case TEX_NOISE: case TEX_MUSGRAVE: case TEX_VORONOI: case TEX_DISTNOISE: result = new GeneratedTexture(textureStructure, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext); break; case TEX_NONE:// No texture, do nothing break; case TEX_POINTDENSITY: case TEX_VOXELDATA: case TEX_PLUGIN: case TEX_ENVMAP: case TEX_OCEAN: LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, textureStructure.getName() }); break; default: throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + textureStructure.getName()); } if (result != null) { result.setName(textureStructure.getName()); result.setWrap(WrapMode.Repeat); // decide if the mipmaps will be generated switch (blenderContext.getBlenderKey().getMipmapGenerationMethod()) { case ALWAYS_GENERATE: result.setMinFilter(MinFilter.Trilinear); break; case NEVER_GENERATE: break; case GENERATE_WHEN_NEEDED: if ((imaflag & 0x04) != 0) { result.setMinFilter(MinFilter.Trilinear); } break; default: throw new IllegalStateException("Unknown mipmap generation method: " + blenderContext.getBlenderKey().getMipmapGenerationMethod()); } if (type != TEX_IMAGE) {// only generated textures should have this key result.setKey(new GeneratedTextureKey(textureStructure.getName())); } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), textureStructure.getOldMemoryAddress() }); } blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, textureStructure); blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result); } return result; } /** * This class returns a texture read from the file or from packed blender * data. * * @param imageStructure * image structure filled with data * @param imaflag * the image flag * @param blenderContext * the blender context * @return the texture that can be used by JME engine * @throws BlenderFileException * this exception is thrown when the blend file structure is * somehow invalid or corrupted */ public Texture loadImageAsTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException { LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress()); Texture result = null; Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); // if (im == null) { HACK force reaload always, as constructor in else case is destroying the TextureKeys! if ("ID".equals(imageStructure.getType())) { LOGGER.fine("Loading texture from external blend file."); result = (Texture) this.loadLibrary(imageStructure); } else { String texturePath = imageStructure.getFieldValue("name").toString(); Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile"); if (pPackedFile.isNull()) { LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath); result = this.loadImageFromFile(texturePath, imaflag, blenderContext); } else { LOGGER.fine("Packed texture. Reading directly from the blend file!"); Structure packedFile = pPackedFile.fetchData().get(0); Pointer pData = (Pointer) packedFile.getFieldValue("data"); FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress()); blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); // Should the texture be flipped? It works for sinbad .. result = new Texture2D(new ImageLoader().loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); } } //} else { // result = new Texture2D(im); // } if (result != null) {// render result is not being loaded blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, imageStructure); blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result.getImage()); result.setName(imageStructure.getName()); } return result; } /** * 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 */ public AffineTransform createAffineTransform(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); } /** * This method returns the proper pixel position on the image. * * @param pos * the relative position (value of range <0, 1> (both inclusive)) * @param size * the size of the line the pixel lies on (width, heigth or * depth) * @return the integer index of the pixel on the line of the specified width */ public int getPixelPosition(float pos, int size) { float pixelWidth = 1 / (float) size; pos *= size; int result = (int) pos; // here is where we repair floating point operations errors :) if (Math.abs(result - pos) > pixelWidth) { ++result; } return result; } /** * This method returns subimage of the give image. The subimage is * constrained by the rectangle coordinates. The source image is unchanged. * * @param image * the image to be subimaged * @param minX * minimum X position * @param minY * minimum Y position * @param maxX * maximum X position * @param maxY * maximum Y position * @return a part of the given image */ public Image getSubimage(Image image, int minX, int minY, int maxX, int maxY) { if (minY > maxY) { throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); } if (minX > maxX) { throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); } if (image.getData().size() > 1) { throw new IllegalArgumentException("Only flat images are allowed for subimage operation!"); } if (image.getMipMapSizes() != null) { LOGGER.warning("Subimaging image with mipmaps is not yet supported!"); } int width = maxX - minX; int height = maxY - minY; ByteBuffer data = BufferUtils.createByteBuffer(width * height * (image.getFormat().getBitsPerPixel() >> 3)); Image result = new Image(image.getFormat(), width, height, data, ColorSpace.sRGB); PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); TexturePixel pixel = new TexturePixel(); for (int x = minX; x < maxX; ++x) { for (int y = minY; y < maxY; ++y) { pixelIO.read(image, 0, pixel, x, y); pixelIO.write(result, 0, pixel, x - minX, y - minY); } } 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(); int depth = image.getDepth() == 0 ? 1 : image.getDepth(); if (colorBand != null) { TexturePixel pixel = new TexturePixel(); PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat()); for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { for (int x = 0; x < image.getWidth(); ++x) { for (int y = 0; y < image.getHeight(); ++y) { imageIO.read(image, layerIndex, 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, layerIndex, 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 layerIndex = 0; layerIndex < depth; ++layerIndex) { for (int x = 0; x < image.getWidth(); ++x) { for (int y = 0; y < image.getHeight(); ++y) { imageIO.read(image, layerIndex, pixel, x, y); pixel.red *= rfac; pixel.green *= gfac; pixel.blue *= bfac; imageIO.write(image, layerIndex, pixel, x, y); } } } } } /** * This method loads the textre from outside the blend file using the * AssetManager that the blend file was loaded with. It returns a texture * with a full assetKey that references the original texture so it later * doesn't need to ba packed when the model data is serialized. It searches * the AssetManager for the full path if the model file is a relative path * and will attempt to truncate the path if it is an absolute file path * until the path can be found in the AssetManager. If the texture can not * be found, it will issue a load attempt for the initial path anyway so the * failed load can be reported by the AssetManagers callback methods for * failed assets. * * @param name * the path to the image * @param imaflag * the image flag * @param blenderContext * the blender context * @return the loaded image or null if the image cannot be found */ protected Texture loadImageFromFile(String name, int imaflag, BlenderContext blenderContext) { if (!name.contains(".")) { return null; // no extension means not a valid image } // decide if the mipmaps will be generated boolean generateMipmaps = false; switch (blenderContext.getBlenderKey().getMipmapGenerationMethod()) { case ALWAYS_GENERATE: generateMipmaps = true; break; case NEVER_GENERATE: break; case GENERATE_WHEN_NEEDED: generateMipmaps = (imaflag & 0x04) != 0; break; default: throw new IllegalStateException("Unknown mipmap generation method: " + blenderContext.getBlenderKey().getMipmapGenerationMethod()); } AssetManager assetManager = blenderContext.getAssetManager(); name = name.replace('\\', '/'); Texture result = null; if (name.startsWith("//")) { // This is a relative path, so try to find it relative to the .blend file String relativePath = name.substring(2); // Augument the path with blender key path BlenderKey blenderKey = blenderContext.getBlenderKey(); int idx = blenderKey.getName().lastIndexOf('/'); String blenderAssetFolder = blenderKey.getName().substring(0, idx != -1 ? idx : 0); String absoluteName = blenderAssetFolder + '/' + relativePath; // Directly try to load texture so AssetManager can report missing textures try { TextureKey key = new TextureKey(absoluteName); key.setFlipY(true); key.setGenerateMips(generateMipmaps); result = assetManager.loadTexture(key); result.setKey(key); } catch (AssetNotFoundException | AssetLoadException e) { LOGGER.fine(e.getLocalizedMessage()); } } else { // This is a full path, try to truncate it until the file can be found // this works as the assetManager root is most probably a part of the // image path. E.g. AssetManager has a locator at c:/Files/ and the // texture path is c:/Files/Textures/Models/Image.jpg. // For this we create a list with every possible full path name from // the asset name to the root. Image.jpg, Models/Image.jpg, // Textures/Models/Image.jpg (bingo) etc. List<String> assetNames = new ArrayList<String>(); String[] paths = name.split("\\/"); StringBuilder sb = new StringBuilder(paths[paths.length - 1]);// the asset name assetNames.add(paths[paths.length - 1]); for (int i = paths.length - 2; i >= 0; --i) { sb.insert(0, '/'); sb.insert(0, paths[i]); assetNames.add(0, sb.toString()); } // Now try to locate the asset for (String assetName : assetNames) { try { TextureKey key = new TextureKey(assetName); key.setFlipY(true); key.setGenerateMips(generateMipmaps); AssetInfo info = assetManager.locateAsset(key); if (info != null) { Texture texture = assetManager.loadTexture(key); result = texture; // Set key explicitly here if other ways fail texture.setKey(key); // If texture is found return it; return result; } } catch (AssetNotFoundException | AssetLoadException e) { LOGGER.fine(e.getLocalizedMessage()); } } // The asset was not found in the loop above, call loadTexture with // the original path once anyway so that the AssetManager can report // the missing asset to subsystems. try { TextureKey key = new TextureKey(name); assetManager.loadTexture(key); } catch (AssetNotFoundException | AssetLoadException e) { LOGGER.fine(e.getLocalizedMessage()); } } return result; } /** * Reads the texture data from the given material or sky structure. * @param structure * the structure of material or sky * @param diffuseColorArray * array of diffuse colors * @param skyTexture * indicates it we're going to read sky texture or not * @return a list of combined textures * @throws BlenderFileException * an exception is thrown when problems with reading the blend file occur */ @SuppressWarnings("unchecked") public List<CombinedTexture> readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException { DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex"); int separatedTextures = skyTexture ? 0 : ((Number) structure.getFieldValue("septex")).intValue(); List<TextureData> texturesList = new ArrayList<TextureData>(); for (int i = 0; i < mtexsArray.getTotalSize(); ++i) { Pointer p = mtexsArray.get(i); if (p.isNotNull() && (separatedTextures & 1 << i) == 0) { TextureData textureData = new TextureData(); textureData.mtex = p.fetchData().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().get(0); textureData.textureStructure = tex; texturesList.add(textureData); } } } LOGGER.info("Loading model's textures."); List<CombinedTexture> loadedTextures = new ArrayList<CombinedTexture>(); if (blenderContext.getBlenderKey().isOptimiseTextures()) { LOGGER.fine("Optimising the useage of model's textures."); Map<Number, List<TextureData>> textureDataMap = this.sortTextures(texturesList); for (Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) { if (entry.getValue().size() > 0) { CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue(), !skyTexture); for (TextureData textureData : entry.getValue()) { int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); boolean negateTexture = (texflag & 0x04) != 0; Texture texture = this.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); if (texture != null) { int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac); combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, textureData.uvCoordinatesName, blenderContext); } } if (combinedTexture.getTexturesCount() > 0) { loadedTextures.add(combinedTexture); } } } } else { LOGGER.fine("No textures optimisation applied."); int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB }; for (TextureData textureData : texturesList) { Texture texture = this.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); if (texture != null) { Number mapto = (Number) textureData.mtex.getFieldValue("mapto"); int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); boolean negateTexture = (texflag & 0x04) != 0; for (int i = 0; i < mappings.length; ++i) { if ((mappings[i] & mapto.intValue()) != 0) { CombinedTexture combinedTexture = new CombinedTexture(mappings[i], !skyTexture); 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) {// the added texture might not have been accepted (if for example loading generated textures is disabled) loadedTextures.add(combinedTexture); } } } } } } return loadedTextures; } /** * This method sorts the textures by their mapping type. In each group only * textures of one type are put (either two- or three-dimensional). * * @return a map with sorted textures */ private Map<Number, List<TextureData>> sortTextures(List<TextureData> textures) { int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB }; Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>(); for (TextureData data : textures) { Number mapto = (Number) data.mtex.getFieldValue("mapto"); for (int i = 0; i < mappings.length; ++i) { if ((mappings[i] & mapto.intValue()) != 0) { List<TextureData> datas = result.get(mappings[i]); if (datas == null) { datas = new ArrayList<TextureData>(); result.put(mappings[i], datas); } datas.add(data); } } } return result; } private 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; } }