1 /* 2 * Copyright (c) 2009-2010 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 33 package com.jme3.scene.plugins; 34 35 import com.jme3.asset.*; 36 import com.jme3.material.Material; 37 import com.jme3.material.MaterialList; 38 import com.jme3.material.RenderState.BlendMode; 39 import com.jme3.math.ColorRGBA; 40 import com.jme3.texture.Texture; 41 import com.jme3.texture.Texture.WrapMode; 42 import com.jme3.texture.Texture2D; 43 import com.jme3.util.PlaceholderAssets; 44 import java.io.File; 45 import java.io.IOException; 46 import java.io.InputStream; 47 import java.util.Locale; 48 import java.util.NoSuchElementException; 49 import java.util.Scanner; 50 import java.util.logging.Level; 51 import java.util.logging.Logger; 52 53 public class MTLLoader implements AssetLoader { 54 55 private static final Logger logger = Logger.getLogger(MTLLoader.class.getName()); 56 57 protected Scanner scan; 58 protected MaterialList matList; 59 //protected Material material; 60 protected AssetManager assetManager; 61 protected String folderName; 62 protected AssetKey key; 63 64 protected Texture diffuseMap, normalMap, specularMap, alphaMap; 65 protected ColorRGBA ambient = new ColorRGBA(); 66 protected ColorRGBA diffuse = new ColorRGBA(); 67 protected ColorRGBA specular = new ColorRGBA(); 68 protected float shininess = 16; 69 protected boolean shadeless; 70 protected String matName; 71 protected float alpha = 1; 72 protected boolean transparent = false; 73 protected boolean disallowTransparency = false; 74 protected boolean disallowAmbient = false; 75 protected boolean disallowSpecular = false; 76 reset()77 public void reset(){ 78 scan = null; 79 matList = null; 80 // material = null; 81 82 resetMaterial(); 83 } 84 readColor()85 protected ColorRGBA readColor(){ 86 ColorRGBA v = new ColorRGBA(); 87 v.set(scan.nextFloat(), scan.nextFloat(), scan.nextFloat(), 1.0f); 88 return v; 89 } 90 nextStatement()91 protected String nextStatement(){ 92 scan.useDelimiter("\n"); 93 String result = scan.next(); 94 scan.useDelimiter("\\p{javaWhitespace}+"); 95 return result; 96 } 97 skipLine()98 protected boolean skipLine(){ 99 try { 100 scan.skip(".*\r{0,1}\n"); 101 return true; 102 } catch (NoSuchElementException ex){ 103 // EOF 104 return false; 105 } 106 } 107 resetMaterial()108 protected void resetMaterial(){ 109 ambient.set(ColorRGBA.DarkGray); 110 diffuse.set(ColorRGBA.LightGray); 111 specular.set(ColorRGBA.Black); 112 shininess = 16; 113 disallowTransparency = false; 114 disallowAmbient = false; 115 disallowSpecular = false; 116 shadeless = false; 117 transparent = false; 118 matName = null; 119 diffuseMap = null; 120 specularMap = null; 121 normalMap = null; 122 alphaMap = null; 123 alpha = 1; 124 } 125 createMaterial()126 protected void createMaterial(){ 127 Material material; 128 129 if (alpha < 1f && transparent && !disallowTransparency){ 130 diffuse.a = alpha; 131 } 132 133 if (shadeless){ 134 material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 135 material.setColor("Color", diffuse.clone()); 136 material.setTexture("ColorMap", diffuseMap); 137 // TODO: Add handling for alpha map? 138 }else{ 139 material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); 140 material.setBoolean("UseMaterialColors", true); 141 material.setColor("Ambient", ambient.clone()); 142 material.setColor("Diffuse", diffuse.clone()); 143 material.setColor("Specular", specular.clone()); 144 material.setFloat("Shininess", shininess); // prevents "premature culling" bug 145 146 if (diffuseMap != null) material.setTexture("DiffuseMap", diffuseMap); 147 if (specularMap != null) material.setTexture("SpecularMap", specularMap); 148 if (normalMap != null) material.setTexture("NormalMap", normalMap); 149 if (alphaMap != null) material.setTexture("AlphaMap", alphaMap); 150 } 151 152 if (transparent && !disallowTransparency){ 153 material.setTransparent(true); 154 material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); 155 material.getAdditionalRenderState().setAlphaTest(true); 156 material.getAdditionalRenderState().setAlphaFallOff(0.01f); 157 } 158 159 matList.put(matName, material); 160 } 161 startMaterial(String name)162 protected void startMaterial(String name){ 163 if (matName != null){ 164 // material is already in cache, generate it 165 createMaterial(); 166 } 167 168 // now, reset the params and set the name to start a new material 169 resetMaterial(); 170 matName = name; 171 } 172 loadTexture(String path)173 protected Texture loadTexture(String path){ 174 String[] split = path.trim().split("\\p{javaWhitespace}+"); 175 176 // will crash if path is an empty string 177 path = split[split.length-1]; 178 179 String name = new File(path).getName(); 180 TextureKey texKey = new TextureKey(folderName + name); 181 texKey.setGenerateMips(true); 182 Texture texture; 183 try { 184 texture = assetManager.loadTexture(texKey); 185 texture.setWrap(WrapMode.Repeat); 186 } catch (AssetNotFoundException ex){ 187 logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key}); 188 texture = new Texture2D(PlaceholderAssets.getPlaceholderImage()); 189 } 190 return texture; 191 } 192 readLine()193 protected boolean readLine(){ 194 if (!scan.hasNext()){ 195 return false; 196 } 197 198 String cmd = scan.next().toLowerCase(); 199 if (cmd.startsWith("#")){ 200 // skip entire comment until next line 201 return skipLine(); 202 }else if (cmd.equals("newmtl")){ 203 String name = scan.next(); 204 startMaterial(name); 205 }else if (cmd.equals("ka")){ 206 ambient.set(readColor()); 207 }else if (cmd.equals("kd")){ 208 diffuse.set(readColor()); 209 }else if (cmd.equals("ks")){ 210 specular.set(readColor()); 211 }else if (cmd.equals("ns")){ 212 float shiny = scan.nextFloat(); 213 if (shiny >= 1){ 214 shininess = shiny; /* (128f / 1000f)*/ 215 if (specular.equals(ColorRGBA.Black)){ 216 specular.set(ColorRGBA.White); 217 } 218 }else{ 219 // For some reason blender likes to export Ns 0 statements 220 // Ignore Ns 0 instead of setting it 221 } 222 223 }else if (cmd.equals("d") || cmd.equals("tr")){ 224 alpha = scan.nextFloat(); 225 transparent = true; 226 }else if (cmd.equals("map_ka")){ 227 // ignore it for now 228 return skipLine(); 229 }else if (cmd.equals("map_kd")){ 230 String path = nextStatement(); 231 diffuseMap = loadTexture(path); 232 }else if (cmd.equals("map_bump") || cmd.equals("bump")){ 233 if (normalMap == null){ 234 String path = nextStatement(); 235 normalMap = loadTexture(path); 236 } 237 }else if (cmd.equals("map_ks")){ 238 String path = nextStatement(); 239 specularMap = loadTexture(path); 240 if (specularMap != null){ 241 // NOTE: since specular color is modulated with specmap 242 // make sure we have it set 243 if (specular.equals(ColorRGBA.Black)){ 244 specular.set(ColorRGBA.White); 245 } 246 } 247 }else if (cmd.equals("map_d")){ 248 String path = scan.next(); 249 alphaMap = loadTexture(path); 250 transparent = true; 251 }else if (cmd.equals("illum")){ 252 int mode = scan.nextInt(); 253 254 switch (mode){ 255 case 0: 256 // no lighting 257 shadeless = true; 258 disallowTransparency = true; 259 break; 260 case 1: 261 disallowSpecular = true; 262 disallowTransparency = true; 263 break; 264 case 2: 265 case 3: 266 case 5: 267 case 8: 268 disallowTransparency = true; 269 break; 270 case 4: 271 case 6: 272 case 7: 273 case 9: 274 // Enable transparency 275 // Works best if diffuse map has an alpha channel 276 transparent = true; 277 break; 278 } 279 }else if (cmd.equals("ke") || cmd.equals("ni")){ 280 // Ni: index of refraction - unsupported in jME 281 // Ke: emission color 282 return skipLine(); 283 }else{ 284 logger.log(Level.WARNING, "Unknown statement in MTL! {0}", cmd); 285 return skipLine(); 286 } 287 288 return true; 289 } 290 291 @SuppressWarnings("empty-statement") load(AssetInfo info)292 public Object load(AssetInfo info) throws IOException{ 293 reset(); 294 295 this.key = info.getKey(); 296 this.assetManager = info.getManager(); 297 folderName = info.getKey().getFolder(); 298 matList = new MaterialList(); 299 300 InputStream in = null; 301 try { 302 in = info.openStream(); 303 scan = new Scanner(in); 304 scan.useLocale(Locale.US); 305 306 while (readLine()); 307 } finally { 308 if (in != null){ 309 in.close(); 310 } 311 } 312 313 if (matName != null){ 314 // still have a material in the vars 315 createMaterial(); 316 resetMaterial(); 317 } 318 319 MaterialList list = matList; 320 321 322 323 return list; 324 } 325 } 326