Home | History | Annotate | Download | only in materials
      1 /*
      2  * Copyright (c) 2009-2012 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 package com.jme3.scene.plugins.blender.materials;
     33 
     34 import com.jme3.asset.BlenderKey.FeaturesToLoad;
     35 import com.jme3.material.MatParam;
     36 import com.jme3.material.MatParamTexture;
     37 import com.jme3.material.Material;
     38 import com.jme3.material.RenderState.BlendMode;
     39 import com.jme3.material.RenderState.FaceCullMode;
     40 import com.jme3.math.ColorRGBA;
     41 import com.jme3.math.FastMath;
     42 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
     43 import com.jme3.scene.plugins.blender.BlenderContext;
     44 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
     45 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
     46 import com.jme3.scene.plugins.blender.file.Pointer;
     47 import com.jme3.scene.plugins.blender.file.Structure;
     48 import com.jme3.shader.VarType;
     49 import com.jme3.texture.Image;
     50 import com.jme3.texture.Image.Format;
     51 import com.jme3.texture.Texture;
     52 import com.jme3.texture.Texture.Type;
     53 import com.jme3.util.BufferUtils;
     54 import java.nio.ByteBuffer;
     55 import java.util.HashMap;
     56 import java.util.List;
     57 import java.util.Map;
     58 import java.util.Map.Entry;
     59 import java.util.logging.Level;
     60 import java.util.logging.Logger;
     61 
     62 public class MaterialHelper extends AbstractBlenderHelper {
     63 	private static final Logger					LOGGER					= Logger.getLogger(MaterialHelper.class.getName());
     64 	protected static final float				DEFAULT_SHININESS		= 20.0f;
     65 
     66 	public static final String					TEXTURE_TYPE_3D			= "Texture";
     67 	public static final String					TEXTURE_TYPE_COLOR		= "ColorMap";
     68 	public static final String					TEXTURE_TYPE_DIFFUSE	= "DiffuseMap";
     69 	public static final String					TEXTURE_TYPE_NORMAL		= "NormalMap";
     70 	public static final String					TEXTURE_TYPE_SPECULAR	= "SpecularMap";
     71 	public static final String					TEXTURE_TYPE_GLOW		= "GlowMap";
     72 	public static final String					TEXTURE_TYPE_ALPHA		= "AlphaMap";
     73 
     74 	public static final Integer					ALPHA_MASK_NONE			= Integer.valueOf(0);
     75 	public static final Integer					ALPHA_MASK_CIRCLE		= Integer.valueOf(1);
     76 	public static final Integer					ALPHA_MASK_CONE			= Integer.valueOf(2);
     77 	public static final Integer					ALPHA_MASK_HYPERBOLE	= Integer.valueOf(3);
     78 	protected final Map<Integer, IAlphaMask>	alphaMasks				= new HashMap<Integer, IAlphaMask>();
     79 
     80 	/**
     81 	 * The type of the material's diffuse shader.
     82 	 */
     83 	public static enum DiffuseShader {
     84 		LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL
     85 	}
     86 
     87 	/**
     88 	 * The type of the material's specular shader.
     89 	 */
     90 	public static enum SpecularShader {
     91 		COOKTORRENCE, PHONG, BLINN, TOON, WARDISO
     92 	}
     93 
     94 	/** Face cull mode. Should be excplicitly set before this helper is used. */
     95 	protected FaceCullMode	faceCullMode;
     96 
     97 	/**
     98 	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
     99 	 * versions.
    100 	 *
    101 	 * @param blenderVersion
    102 	 *        the version read from the blend file
    103 	 * @param fixUpAxis
    104      *        a variable that indicates if the Y asxis is the UP axis or not
    105 	 */
    106 	public MaterialHelper(String blenderVersion, boolean fixUpAxis) {
    107 		super(blenderVersion, false);
    108 		// setting alpha masks
    109 		alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() {
    110 			@Override
    111 			public void setImageSize(int width, int height) {}
    112 
    113 			@Override
    114 			public byte getAlpha(float x, float y) {
    115 				return (byte) 255;
    116 			}
    117 		});
    118 		alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() {
    119 			private float	r;
    120 			private float[]	center;
    121 
    122 			@Override
    123 			public void setImageSize(int width, int height) {
    124 				r = Math.min(width, height) * 0.5f;
    125 				center = new float[] { width * 0.5f, height * 0.5f };
    126 			}
    127 
    128 			@Override
    129 			public byte getAlpha(float x, float y) {
    130 				float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));
    131 				return (byte) (d >= r ? 0 : 255);
    132 			}
    133 		});
    134 		alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() {
    135 			private float	r;
    136 			private float[]	center;
    137 
    138 			@Override
    139 			public void setImageSize(int width, int height) {
    140 				r = Math.min(width, height) * 0.5f;
    141 				center = new float[] { width * 0.5f, height * 0.5f };
    142 			}
    143 
    144 			@Override
    145 			public byte getAlpha(float x, float y) {
    146 				float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));
    147 				return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f);
    148 			}
    149 		});
    150 		alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() {
    151 			private float	r;
    152 			private float[]	center;
    153 
    154 			@Override
    155 			public void setImageSize(int width, int height) {
    156 				r = Math.min(width, height) * 0.5f;
    157 				center = new float[] { width * 0.5f, height * 0.5f };
    158 			}
    159 
    160 			@Override
    161 			public byte getAlpha(float x, float y) {
    162 				float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r;
    163 				return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f);
    164 			}
    165 		});
    166 	}
    167 
    168 	/**
    169 	 * This method sets the face cull mode to be used with every loaded material.
    170 	 *
    171 	 * @param faceCullMode
    172 	 *        the face cull mode
    173 	 */
    174 	public void setFaceCullMode(FaceCullMode faceCullMode) {
    175 		this.faceCullMode = faceCullMode;
    176 	}
    177 
    178 	/**
    179 	 * This method converts the material structure to jme Material.
    180 	 * @param structure
    181 	 *        structure with material data
    182 	 * @param blenderContext
    183 	 *        the blender context
    184 	 * @return jme material
    185 	 * @throws BlenderFileException
    186 	 *         an exception is throw when problems with blend file occur
    187 	 */
    188 	public Material toMaterial(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
    189 		LOGGER.log(Level.INFO, "Loading material.");
    190 		if (structure == null) {
    191 			return blenderContext.getDefaultMaterial();
    192 		}
    193 		Material result = (Material) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
    194 		if (result != null) {
    195 			return result;
    196 		}
    197 
    198 		MaterialContext materialContext = new MaterialContext(structure, blenderContext);
    199 		LOGGER.log(Level.INFO, "Material's name: {0}", materialContext.name);
    200 
    201 		if(materialContext.textures.size() > 1) {
    202 			LOGGER.log(Level.WARNING, "Attetion! Many textures found for material: {0}. Only the first of each supported mapping types will be used!", materialContext.name);
    203 		}
    204 
    205 		// texture
    206 		Type colorTextureType = null;
    207 		Map<String, Texture> texturesMap = new HashMap<String, Texture>();
    208 		for(Entry<Number, Texture> textureEntry : materialContext.loadedTextures.entrySet()) {
    209 			int mapto = textureEntry.getKey().intValue();
    210 			Texture texture = textureEntry.getValue();
    211 			if ((mapto & MaterialContext.MTEX_COL) != 0) {
    212 				colorTextureType = texture.getType();
    213 				if (materialContext.shadeless) {
    214 					texturesMap.put(colorTextureType==Type.ThreeDimensional ? TEXTURE_TYPE_3D : TEXTURE_TYPE_COLOR, texture);
    215 				} else {
    216 					texturesMap.put(colorTextureType==Type.ThreeDimensional ? TEXTURE_TYPE_3D : TEXTURE_TYPE_DIFFUSE, texture);
    217 				}
    218 			}
    219 			if(texture.getType()==Type.TwoDimensional) {//so far only 2D textures can be mapped in other way than color
    220 				if ((mapto & MaterialContext.MTEX_NOR) != 0 && !materialContext.shadeless) {
    221 					//Structure mTex = materialContext.getMTex(texture);
    222 					//Texture normalMapTexture = textureHelper.convertToNormalMapTexture(texture, ((Number) mTex.getFieldValue("norfac")).floatValue());
    223 					//texturesMap.put(TEXTURE_TYPE_NORMAL, normalMapTexture);
    224                                         texturesMap.put(TEXTURE_TYPE_NORMAL, texture);
    225 				}
    226 				if ((mapto & MaterialContext.MTEX_EMIT) != 0) {
    227 					texturesMap.put(TEXTURE_TYPE_GLOW, texture);
    228 				}
    229 				if ((mapto & MaterialContext.MTEX_SPEC) != 0 && !materialContext.shadeless) {
    230 					texturesMap.put(TEXTURE_TYPE_SPECULAR, texture);
    231 				}
    232 				if ((mapto & MaterialContext.MTEX_ALPHA) != 0 && !materialContext.shadeless) {
    233 					texturesMap.put(TEXTURE_TYPE_ALPHA, texture);
    234 				}
    235 			}
    236 		}
    237 
    238 		//creating the material
    239 		if(colorTextureType==Type.ThreeDimensional) {
    240 			result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Texture3D/tex3D.j3md");
    241 		} else {
    242 			if (materialContext.shadeless) {
    243 				result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
    244 
    245                 if (!materialContext.transparent) {
    246                     materialContext.diffuseColor.a = 1;
    247                 }
    248 
    249                 result.setColor("Color", materialContext.diffuseColor);
    250 			} else {
    251 				result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
    252 				result.setBoolean("UseMaterialColors", Boolean.TRUE);
    253 
    254 				// setting the colors
    255 				result.setBoolean("Minnaert", materialContext.diffuseShader == DiffuseShader.MINNAERT);
    256 				if (!materialContext.transparent) {
    257 					materialContext.diffuseColor.a = 1;
    258 				}
    259 				result.setColor("Diffuse", materialContext.diffuseColor);
    260 
    261 				result.setBoolean("WardIso", materialContext.specularShader == SpecularShader.WARDISO);
    262 				result.setColor("Specular", materialContext.specularColor);
    263 
    264 				result.setColor("Ambient", materialContext.ambientColor);
    265 				result.setFloat("Shininess", materialContext.shininess);
    266 			}
    267 
    268 			if (materialContext.vertexColor) {
    269 				result.setBoolean(materialContext.shadeless ? "VertexColor" : "UseVertexColor", true);
    270 			}
    271 		}
    272 
    273 		//applying textures
    274 		for(Entry<String, Texture> textureEntry : texturesMap.entrySet()) {
    275 			result.setTexture(textureEntry.getKey(), textureEntry.getValue());
    276 		}
    277 
    278 		//applying other data
    279 		result.getAdditionalRenderState().setFaceCullMode(faceCullMode);
    280 		if (materialContext.transparent) {
    281 			result.setTransparent(true);
    282 			result.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
    283 		}
    284 
    285 		result.setName(materialContext.getName());
    286 		blenderContext.setMaterialContext(result, materialContext);
    287 		blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result);
    288 		return result;
    289 	}
    290 
    291 	/**
    292 	 * This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but
    293 	 * returned itself.
    294 	 *
    295 	 * @param material
    296 	 *        a material to be cloned without textures
    297 	 * @param imageType
    298 	 *        type of image defined by blender; the constants are defined in TextureHelper
    299 	 * @return material without textures of a specified type
    300 	 */
    301 	public Material getNonTexturedMaterial(Material material, int imageType) {
    302 		String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, TEXTURE_TYPE_ALPHA };
    303 		Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length);
    304 		for (String textureParamName : textureParamNames) {
    305 			MatParamTexture matParamTexture = material.getTextureParam(textureParamName);
    306 			if (matParamTexture != null) {
    307 				textures.put(textureParamName, matParamTexture.getTextureValue());
    308 			}
    309 		}
    310 		if (textures.isEmpty()) {
    311 			return material;
    312 		} else {
    313 			// clear all textures first so that wo de not waste resources cloning them
    314 			for (Entry<String, Texture> textureParamName : textures.entrySet()) {
    315 				String name = textureParamName.getValue().getName();
    316 				try {
    317 					int type = Integer.parseInt(name);
    318 					if (type == imageType) {
    319 						material.clearParam(textureParamName.getKey());
    320 					}
    321 				} catch (NumberFormatException e) {
    322 					LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", name);
    323 				}
    324 			}
    325 			Material result = material.clone();
    326 			// put the textures back in place
    327 			for (Entry<String, Texture> textureEntry : textures.entrySet()) {
    328 				material.setTexture(textureEntry.getKey(), textureEntry.getValue());
    329 			}
    330 			return result;
    331 		}
    332 	}
    333 
    334 	/**
    335 	 * This method converts the given material into particles-usable material.
    336 	 * The texture and glow color are being copied.
    337 	 * The method assumes it receives the Lighting type of material.
    338 	 * @param material
    339 	 *        the source material
    340 	 * @param blenderContext
    341 	 *        the blender context
    342 	 * @return material converted into particles-usable material
    343 	 */
    344 	public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) {
    345 		Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
    346 
    347 		// copying texture
    348 		MatParam diffuseMap = material.getParam("DiffuseMap");
    349 		if (diffuseMap != null) {
    350 			Texture texture = ((Texture) diffuseMap.getValue()).clone();
    351 
    352 			// applying alpha mask to the texture
    353 			Image image = texture.getImage();
    354 			ByteBuffer sourceBB = image.getData(0);
    355 			sourceBB.rewind();
    356 			int w = image.getWidth();
    357 			int h = image.getHeight();
    358 			ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4);
    359 			IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex);
    360 			iAlphaMask.setImageSize(w, h);
    361 
    362 			for (int x = 0; x < w; ++x) {
    363 				for (int y = 0; y < h; ++y) {
    364 					bb.put(sourceBB.get());
    365 					bb.put(sourceBB.get());
    366 					bb.put(sourceBB.get());
    367 					bb.put(iAlphaMask.getAlpha(x, y));
    368 				}
    369 			}
    370 
    371 			image = new Image(Format.RGBA8, w, h, bb);
    372 			texture.setImage(image);
    373 
    374 			result.setTextureParam("Texture", VarType.Texture2D, texture);
    375 		}
    376 
    377 		// copying glow color
    378 		MatParam glowColor = material.getParam("GlowColor");
    379 		if (glowColor != null) {
    380 			ColorRGBA color = (ColorRGBA) glowColor.getValue();
    381 			result.setParam("GlowColor", VarType.Vector3, color);
    382 		}
    383 		return result;
    384 	}
    385 
    386 	/**
    387 	 * This method indicates if the material has any kind of texture.
    388 	 *
    389 	 * @param material
    390 	 *        the material
    391 	 * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise
    392 	 */
    393 	public boolean hasTexture(Material material) {
    394 		if (material != null) {
    395 			if (material.getTextureParam(TEXTURE_TYPE_3D) != null) {
    396 				return true;
    397 			}
    398 			if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != null) {
    399 				return true;
    400 			}
    401 			if (material.getTextureParam(TEXTURE_TYPE_COLOR) != null) {
    402 				return true;
    403 			}
    404 			if (material.getTextureParam(TEXTURE_TYPE_DIFFUSE) != null) {
    405 				return true;
    406 			}
    407 			if (material.getTextureParam(TEXTURE_TYPE_GLOW) != null) {
    408 				return true;
    409 			}
    410 			if (material.getTextureParam(TEXTURE_TYPE_NORMAL) != null) {
    411 				return true;
    412 			}
    413 			if (material.getTextureParam(TEXTURE_TYPE_SPECULAR) != null) {
    414 				return true;
    415 			}
    416 		}
    417 		return false;
    418 	}
    419 
    420 	/**
    421 	 * This method indicates if the material has a texture of a specified type.
    422 	 *
    423 	 * @param material
    424 	 *        the material
    425 	 * @param textureType
    426 	 *        the type of the texture
    427 	 * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise
    428 	 */
    429 	public boolean hasTexture(Material material, String textureType) {
    430 		if (material != null) {
    431 			return material.getTextureParam(textureType) != null;
    432 		}
    433 		return false;
    434 	}
    435 
    436 	/**
    437 	 * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or
    438 	 * curve) but needs to have 'mat' field/
    439 	 *
    440 	 * @param structureWithMaterials
    441 	 *        the structure containing the mesh data
    442 	 * @param blenderContext
    443 	 *        the blender context
    444 	 * @return a list of vertices colors, each color belongs to a single vertex
    445 	 * @throws BlenderFileException
    446 	 *         this exception is thrown when the blend file structure is somehow invalid or corrupted
    447 	 */
    448 	public Material[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException {
    449 		Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat");
    450 		Material[] materials = null;
    451 		if (ppMaterials.isNotNull()) {
    452 			List<Structure> materialStructures = ppMaterials.fetchData(blenderContext.getInputStream());
    453 			if (materialStructures != null && materialStructures.size() > 0) {
    454 				MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
    455 				materials = new Material[materialStructures.size()];
    456 				int i = 0;
    457 				for (Structure s : materialStructures) {
    458 					Material material = (Material) blenderContext.getLoadedFeature(s.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
    459 					if (material == null) {
    460 						material = materialHelper.toMaterial(s, blenderContext);
    461 					}
    462 					materials[i++] = material;
    463 				}
    464 			}
    465 		}
    466 		return materials;
    467 	}
    468 
    469 	/**
    470 	 * This method converts rgb values to hsv values.
    471 	 *
    472 	 * @param r
    473 	 *        red value of the color
    474          * @param g
    475          *        green value of the color
    476          * @param b
    477          *        blue value of the color
    478 	 * @param hsv
    479 	 *        hsv values of a color (this table contains the result of the transformation)
    480 	 */
    481 	public void rgbToHsv(float r, float g, float b, float[] hsv) {
    482 		float cmax = r;
    483 		float cmin = r;
    484 		cmax = g > cmax ? g : cmax;
    485 		cmin = g < cmin ? g : cmin;
    486 		cmax = b > cmax ? b : cmax;
    487 		cmin = b < cmin ? b : cmin;
    488 
    489 		hsv[2] = cmax; /* value */
    490 		if (cmax != 0.0) {
    491 			hsv[1] = (cmax - cmin) / cmax;
    492 		} else {
    493 			hsv[1] = 0.0f;
    494 			hsv[0] = 0.0f;
    495 		}
    496 		if (hsv[1] == 0.0) {
    497 			hsv[0] = -1.0f;
    498 		} else {
    499 			float cdelta = cmax - cmin;
    500 			float rc = (cmax - r) / cdelta;
    501 			float gc = (cmax - g) / cdelta;
    502 			float bc = (cmax - b) / cdelta;
    503 			if (r == cmax) {
    504 				hsv[0] = bc - gc;
    505 			} else if (g == cmax) {
    506 				hsv[0] = 2.0f + rc - bc;
    507 			} else {
    508 				hsv[0] = 4.0f + gc - rc;
    509 			}
    510 			hsv[0] *= 60.0f;
    511 			if (hsv[0] < 0.0f) {
    512 				hsv[0] += 360.0f;
    513 			}
    514 		}
    515 
    516 		hsv[0] /= 360.0f;
    517 		if (hsv[0] < 0.0f) {
    518 			hsv[0] = 0.0f;
    519 		}
    520 	}
    521 
    522 	/**
    523 	 * This method converts rgb values to hsv values.
    524 	 *
    525 	 * @param h
    526 	 *        hue
    527 	 * @param s
    528 	 *        saturation
    529 	 * @param v
    530 	 *        value
    531 	 * @param rgb
    532 	 *        rgb result vector (should have 3 elements)
    533 	 */
    534 	public void hsvToRgb(float h, float s, float v, float[] rgb) {
    535 		h *= 360.0f;
    536 		if (s == 0.0) {
    537 			rgb[0] = rgb[1] = rgb[2] = v;
    538 		} else {
    539 			if (h == 360) {
    540 				h = 0;
    541 			} else {
    542 				h /= 60;
    543 			}
    544 			int i = (int) Math.floor(h);
    545 			float f = h - i;
    546 			float p = v * (1.0f - s);
    547 			float q = v * (1.0f - s * f);
    548 			float t = v * (1.0f - s * (1.0f - f));
    549 			switch (i) {
    550 				case 0:
    551 					rgb[0] = v;
    552 					rgb[1] = t;
    553 					rgb[2] = p;
    554 					break;
    555 				case 1:
    556 					rgb[0] = q;
    557 					rgb[1] = v;
    558 					rgb[2] = p;
    559 					break;
    560 				case 2:
    561 					rgb[0] = p;
    562 					rgb[1] = v;
    563 					rgb[2] = t;
    564 					break;
    565 				case 3:
    566 					rgb[0] = p;
    567 					rgb[1] = q;
    568 					rgb[2] = v;
    569 					break;
    570 				case 4:
    571 					rgb[0] = t;
    572 					rgb[1] = p;
    573 					rgb[2] = v;
    574 					break;
    575 				case 5:
    576 					rgb[0] = v;
    577 					rgb[1] = p;
    578 					rgb[2] = q;
    579 					break;
    580 			}
    581 		}
    582 	}
    583 
    584 	@Override
    585 	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
    586 		return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0;
    587 	}
    588 }
    589