Home | History | Annotate | Download | only in plugins
      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 
     77     public void reset(){
     78         scan = null;
     79         matList = null;
     80 //        material = null;
     81 
     82         resetMaterial();
     83     }
     84 
     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 
     91     protected String nextStatement(){
     92         scan.useDelimiter("\n");
     93         String result = scan.next();
     94         scan.useDelimiter("\\p{javaWhitespace}+");
     95         return result;
     96     }
     97 
     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 
    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 
    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 
    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 
    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 
    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")
    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