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.material.plugins;
     34 
     35 import com.jme3.asset.*;
     36 import com.jme3.material.RenderState.BlendMode;
     37 import com.jme3.material.RenderState.FaceCullMode;
     38 import com.jme3.material.*;
     39 import com.jme3.material.TechniqueDef.LightMode;
     40 import com.jme3.material.TechniqueDef.ShadowMode;
     41 import com.jme3.math.ColorRGBA;
     42 import com.jme3.math.Vector2f;
     43 import com.jme3.math.Vector3f;
     44 import com.jme3.shader.VarType;
     45 import com.jme3.texture.Texture;
     46 import com.jme3.texture.Texture.WrapMode;
     47 import com.jme3.texture.Texture2D;
     48 import com.jme3.util.PlaceholderAssets;
     49 import com.jme3.util.blockparser.BlockLanguageParser;
     50 import com.jme3.util.blockparser.Statement;
     51 import java.io.IOException;
     52 import java.io.InputStream;
     53 import java.util.List;
     54 import java.util.logging.Level;
     55 import java.util.logging.Logger;
     56 
     57 public class J3MLoader implements AssetLoader {
     58 
     59     private static final Logger logger = Logger.getLogger(J3MLoader.class.getName());
     60 
     61     private AssetManager assetManager;
     62     private AssetKey key;
     63 
     64     private MaterialDef materialDef;
     65     private Material material;
     66     private TechniqueDef technique;
     67     private RenderState renderState;
     68 
     69     private String shaderLang;
     70     private String vertName;
     71     private String fragName;
     72 
     73     private static final String whitespacePattern = "\\p{javaWhitespace}+";
     74 
     75     public J3MLoader(){
     76     }
     77 
     78     private void throwIfNequal(String expected, String got) throws IOException {
     79         if (expected == null)
     80             throw new IOException("Expected a statement, got '"+got+"'!");
     81 
     82         if (!expected.equals(got))
     83             throw new IOException("Expected '"+expected+"', got '"+got+"'!");
     84     }
     85 
     86     // <TYPE> <LANG> : <SOURCE>
     87     private void readShaderStatement(String statement) throws IOException {
     88         String[] split = statement.split(":");
     89         if (split.length != 2){
     90             throw new IOException("Shader statement syntax incorrect" + statement);
     91         }
     92         String[] typeAndLang = split[0].split(whitespacePattern);
     93         if (typeAndLang.length != 2){
     94             throw new IOException("Shader statement syntax incorrect: " + statement);
     95         }
     96         shaderLang = typeAndLang[1];
     97         if (typeAndLang[0].equals("VertexShader")){
     98             vertName = split[1].trim();
     99         }else if (typeAndLang[0].equals("FragmentShader")){
    100             fragName = split[1].trim();
    101         }
    102     }
    103 
    104     // LightMode <MODE>
    105     private void readLightMode(String statement) throws IOException{
    106         String[] split = statement.split(whitespacePattern);
    107         if (split.length != 2){
    108             throw new IOException("LightMode statement syntax incorrect");
    109         }
    110         LightMode lm = LightMode.valueOf(split[1]);
    111         technique.setLightMode(lm);
    112     }
    113 
    114     // ShadowMode <MODE>
    115     private void readShadowMode(String statement) throws IOException{
    116         String[] split = statement.split(whitespacePattern);
    117         if (split.length != 2){
    118             throw new IOException("ShadowMode statement syntax incorrect");
    119         }
    120         ShadowMode sm = ShadowMode.valueOf(split[1]);
    121         technique.setShadowMode(sm);
    122     }
    123 
    124     private Object readValue(VarType type, String value) throws IOException{
    125         if (type.isTextureType()){
    126 //            String texturePath = readString("[\n;(//)(\\})]");
    127             String texturePath = value.trim();
    128             boolean flipY = false;
    129             boolean repeat = false;
    130             if (texturePath.startsWith("Flip Repeat ")){
    131                 texturePath = texturePath.substring(12).trim();
    132                 flipY = true;
    133                 repeat = true;
    134             }else if (texturePath.startsWith("Flip ")){
    135                 texturePath = texturePath.substring(5).trim();
    136                 flipY = true;
    137             }else if (texturePath.startsWith("Repeat ")){
    138                 texturePath = texturePath.substring(7).trim();
    139                 repeat = true;
    140             }
    141 
    142             TextureKey texKey = new TextureKey(texturePath, flipY);
    143             texKey.setAsCube(type == VarType.TextureCubeMap);
    144             texKey.setGenerateMips(true);
    145 
    146             Texture tex;
    147             try {
    148                 tex = assetManager.loadTexture(texKey);
    149             } catch (AssetNotFoundException ex){
    150                 logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key});
    151                 tex = null;
    152             }
    153             if (tex != null){
    154                 if (repeat){
    155                     tex.setWrap(WrapMode.Repeat);
    156                 }
    157             }else{
    158                 tex = new Texture2D(PlaceholderAssets.getPlaceholderImage());
    159             }
    160             return tex;
    161         }else{
    162             String[] split = value.trim().split(whitespacePattern);
    163             switch (type){
    164                 case Float:
    165                     if (split.length != 1){
    166                         throw new IOException("Float value parameter must have 1 entry: " + value);
    167                     }
    168                      return Float.parseFloat(split[0]);
    169                 case Vector2:
    170                     if (split.length != 2){
    171                         throw new IOException("Vector2 value parameter must have 2 entries: " + value);
    172                     }
    173                     return new Vector2f(Float.parseFloat(split[0]),
    174                                                                Float.parseFloat(split[1]));
    175                 case Vector3:
    176                     if (split.length != 3){
    177                         throw new IOException("Vector3 value parameter must have 3 entries: " + value);
    178                     }
    179                     return new Vector3f(Float.parseFloat(split[0]),
    180                                                                Float.parseFloat(split[1]),
    181                                                                Float.parseFloat(split[2]));
    182                 case Vector4:
    183                     if (split.length != 4){
    184                         throw new IOException("Vector4 value parameter must have 4 entries: " + value);
    185                     }
    186                     return new ColorRGBA(Float.parseFloat(split[0]),
    187                                                                 Float.parseFloat(split[1]),
    188                                                                 Float.parseFloat(split[2]),
    189                                                                 Float.parseFloat(split[3]));
    190                 case Int:
    191                     if (split.length != 1){
    192                         throw new IOException("Int value parameter must have 1 entry: " + value);
    193                     }
    194                     return Integer.parseInt(split[0]);
    195                 case Boolean:
    196                     if (split.length != 1){
    197                         throw new IOException("Boolean value parameter must have 1 entry: " + value);
    198                     }
    199                     return Boolean.parseBoolean(split[0]);
    200                 default:
    201                     throw new UnsupportedOperationException("Unknown type: "+type);
    202             }
    203         }
    204     }
    205 
    206     // <TYPE> <NAME> [ "(" <FFBINDING> ")" ] [ ":" <DEFAULTVAL> ]
    207     private void readParam(String statement) throws IOException{
    208         String name;
    209         String defaultVal = null;
    210         FixedFuncBinding ffBinding = null;
    211 
    212         String[] split = statement.split(":");
    213 
    214         // Parse default val
    215         if (split.length == 1){
    216             // Doesn't contain default value
    217         }else{
    218             if (split.length != 2){
    219                 throw new IOException("Parameter statement syntax incorrect");
    220             }
    221             statement = split[0].trim();
    222             defaultVal = split[1].trim();
    223         }
    224 
    225         // Parse ffbinding
    226         int startParen = statement.indexOf("(");
    227         if (startParen != -1){
    228             // get content inside parentheses
    229             int endParen = statement.indexOf(")", startParen);
    230             String bindingStr = statement.substring(startParen+1, endParen).trim();
    231             try {
    232                 ffBinding = FixedFuncBinding.valueOf(bindingStr);
    233             } catch (IllegalArgumentException ex){
    234                 throw new IOException("FixedFuncBinding '" +
    235                                       split[1] + "' does not exist!");
    236             }
    237             statement = statement.substring(0, startParen);
    238         }
    239 
    240         // Parse type + name
    241         split = statement.split(whitespacePattern);
    242         if (split.length != 2){
    243             throw new IOException("Parameter statement syntax incorrect");
    244         }
    245 
    246         VarType type;
    247         if (split[0].equals("Color")){
    248             type = VarType.Vector4;
    249         }else{
    250             type = VarType.valueOf(split[0]);
    251         }
    252 
    253         name = split[1];
    254 
    255         Object defaultValObj = null;
    256         if (defaultVal != null){
    257             defaultValObj = readValue(type, defaultVal);
    258         }
    259 
    260         materialDef.addMaterialParam(type, name, defaultValObj, ffBinding);
    261     }
    262 
    263     private void readValueParam(String statement) throws IOException{
    264         // Use limit=1 incase filename contains colons
    265         String[] split = statement.split(":", 2);
    266         if (split.length != 2){
    267             throw new IOException("Value parameter statement syntax incorrect");
    268         }
    269         String name = split[0].trim();
    270 
    271         // parse value
    272         MatParam p = material.getMaterialDef().getMaterialParam(name);
    273         if (p == null){
    274             throw new IOException("The material parameter: "+name+" is undefined.");
    275         }
    276 
    277         Object valueObj = readValue(p.getVarType(), split[1]);
    278         if (p.getVarType().isTextureType()){
    279             material.setTextureParam(name, p.getVarType(), (Texture) valueObj);
    280         }else{
    281             material.setParam(name, p.getVarType(), valueObj);
    282         }
    283     }
    284 
    285     private void readMaterialParams(List<Statement> paramsList) throws IOException{
    286         for (Statement statement : paramsList){
    287             readParam(statement.getLine());
    288         }
    289     }
    290 
    291     private void readExtendingMaterialParams(List<Statement> paramsList) throws IOException{
    292         for (Statement statement : paramsList){
    293             readValueParam(statement.getLine());
    294         }
    295     }
    296 
    297     private void readWorldParams(List<Statement> worldParams) throws IOException{
    298         for (Statement statement : worldParams){
    299             technique.addWorldParam(statement.getLine());
    300         }
    301     }
    302 
    303     private boolean parseBoolean(String word){
    304         return word != null && word.equals("On");
    305     }
    306 
    307     private void readRenderStateStatement(String statement) throws IOException{
    308         String[] split = statement.split(whitespacePattern);
    309         if (split[0].equals("Wireframe")){
    310             renderState.setWireframe(parseBoolean(split[1]));
    311         }else if (split[0].equals("FaceCull")){
    312             renderState.setFaceCullMode(FaceCullMode.valueOf(split[1]));
    313         }else if (split[0].equals("DepthWrite")){
    314             renderState.setDepthWrite(parseBoolean(split[1]));
    315         }else if (split[0].equals("DepthTest")){
    316             renderState.setDepthTest(parseBoolean(split[1]));
    317         }else if (split[0].equals("Blend")){
    318             renderState.setBlendMode(BlendMode.valueOf(split[1]));
    319         }else if (split[0].equals("AlphaTestFalloff")){
    320             renderState.setAlphaTest(true);
    321             renderState.setAlphaFallOff(Float.parseFloat(split[1]));
    322         }else if (split[0].equals("PolyOffset")){
    323             float factor = Float.parseFloat(split[1]);
    324             float units = Float.parseFloat(split[2]);
    325             renderState.setPolyOffset(factor, units);
    326         }else if (split[0].equals("ColorWrite")){
    327             renderState.setColorWrite(parseBoolean(split[1]));
    328         }else if (split[0].equals("PointSprite")){
    329             renderState.setPointSprite(parseBoolean(split[1]));
    330         }else{
    331             throwIfNequal(null, split[0]);
    332         }
    333     }
    334 
    335     private void readAdditionalRenderState(List<Statement> renderStates) throws IOException{
    336         renderState = material.getAdditionalRenderState();
    337         for (Statement statement : renderStates){
    338             readRenderStateStatement(statement.getLine());
    339         }
    340         renderState = null;
    341     }
    342 
    343     private void readRenderState(List<Statement> renderStates) throws IOException{
    344         renderState = new RenderState();
    345         for (Statement statement : renderStates){
    346             readRenderStateStatement(statement.getLine());
    347         }
    348         technique.setRenderState(renderState);
    349         renderState = null;
    350     }
    351 
    352     // <DEFINENAME> [ ":" <PARAMNAME> ]
    353     private void readDefine(String statement) throws IOException{
    354         String[] split = statement.split(":");
    355         if (split.length == 1){
    356             // add preset define
    357             technique.addShaderPresetDefine(split[0].trim(), VarType.Boolean, true);
    358         }else if (split.length == 2){
    359             technique.addShaderParamDefine(split[1].trim(), split[0].trim());
    360         }else{
    361             throw new IOException("Define syntax incorrect");
    362         }
    363     }
    364 
    365     private void readDefines(List<Statement> defineList) throws IOException{
    366         for (Statement statement : defineList){
    367             readDefine(statement.getLine());
    368         }
    369 
    370     }
    371 
    372     private void readTechniqueStatement(Statement statement) throws IOException{
    373         String[] split = statement.getLine().split("[ \\{]");
    374         if (split[0].equals("VertexShader") ||
    375             split[0].equals("FragmentShader")){
    376             readShaderStatement(statement.getLine());
    377         }else if (split[0].equals("LightMode")){
    378             readLightMode(statement.getLine());
    379         }else if (split[0].equals("ShadowMode")){
    380             readShadowMode(statement.getLine());
    381         }else if (split[0].equals("WorldParameters")){
    382             readWorldParams(statement.getContents());
    383         }else if (split[0].equals("RenderState")){
    384             readRenderState(statement.getContents());
    385         }else if (split[0].equals("Defines")){
    386             readDefines(statement.getContents());
    387         }else{
    388             throwIfNequal(null, split[0]);
    389         }
    390     }
    391 
    392     private void readTransparentStatement(String statement) throws IOException{
    393         String[] split = statement.split(whitespacePattern);
    394         if (split.length != 2){
    395             throw new IOException("Transparent statement syntax incorrect");
    396         }
    397         material.setTransparent(parseBoolean(split[1]));
    398     }
    399 
    400     private void readTechnique(Statement techStat) throws IOException{
    401         String[] split = techStat.getLine().split(whitespacePattern);
    402         if (split.length == 1){
    403             technique = new TechniqueDef(null);
    404         }else if (split.length == 2){
    405             technique = new TechniqueDef(split[1]);
    406         }else{
    407             throw new IOException("Technique statement syntax incorrect");
    408         }
    409 
    410         for (Statement statement : techStat.getContents()){
    411             readTechniqueStatement(statement);
    412         }
    413 
    414         if (vertName != null && fragName != null){
    415             technique.setShaderFile(vertName, fragName, shaderLang);
    416         }
    417 
    418         materialDef.addTechniqueDef(technique);
    419         technique = null;
    420         vertName = null;
    421         fragName = null;
    422         shaderLang = null;
    423     }
    424 
    425     private void loadFromRoot(List<Statement> roots) throws IOException{
    426         if (roots.size() == 2){
    427             Statement exception = roots.get(0);
    428             String line = exception.getLine();
    429             if (line.startsWith("Exception")){
    430                 throw new AssetLoadException(line.substring("Exception ".length()));
    431             }else{
    432                 throw new IOException("In multiroot material, expected first statement to be 'Exception'");
    433             }
    434         }else if (roots.size() != 1){
    435             throw new IOException("Too many roots in J3M/J3MD file");
    436         }
    437 
    438         boolean extending = false;
    439         Statement materialStat = roots.get(0);
    440         String materialName = materialStat.getLine();
    441         if (materialName.startsWith("MaterialDef")){
    442             materialName = materialName.substring("MaterialDef ".length()).trim();
    443             extending = false;
    444         }else if (materialName.startsWith("Material")){
    445             materialName = materialName.substring("Material ".length()).trim();
    446             extending = true;
    447         }else{
    448             throw new IOException("Specified file is not a Material file");
    449         }
    450 
    451         String[] split = materialName.split(":", 2);
    452 
    453         if (materialName.equals("")){
    454             throw new IOException("Material name cannot be empty");
    455         }
    456 
    457         if (split.length == 2){
    458             if (!extending){
    459                 throw new IOException("Must use 'Material' when extending.");
    460             }
    461 
    462             String extendedMat = split[1].trim();
    463 
    464             MaterialDef def = (MaterialDef) assetManager.loadAsset(new AssetKey(extendedMat));
    465             if (def == null)
    466                 throw new IOException("Extended material "+extendedMat+" cannot be found.");
    467 
    468             material = new Material(def);
    469             material.setKey(key);
    470 //            material.setAssetName(fileName);
    471         }else if (split.length == 1){
    472             if (extending){
    473                 throw new IOException("Expected ':', got '{'");
    474             }
    475             materialDef = new MaterialDef(assetManager, materialName);
    476             // NOTE: pass file name for defs so they can be loaded later
    477             materialDef.setAssetName(key.getName());
    478         }else{
    479             throw new IOException("Cannot use colon in material name/path");
    480         }
    481 
    482         for (Statement statement : materialStat.getContents()){
    483             split = statement.getLine().split("[ \\{]");
    484             String statType = split[0];
    485             if (extending){
    486                 if (statType.equals("MaterialParameters")){
    487                     readExtendingMaterialParams(statement.getContents());
    488                 }else if (statType.equals("AdditionalRenderState")){
    489                     readAdditionalRenderState(statement.getContents());
    490                 }else if (statType.equals("Transparent")){
    491                     readTransparentStatement(statement.getLine());
    492                 }
    493             }else{
    494                 if (statType.equals("Technique")){
    495                     readTechnique(statement);
    496                 }else if (statType.equals("MaterialParameters")){
    497                     readMaterialParams(statement.getContents());
    498                 }else{
    499                     throw new IOException("Expected material statement, got '"+statType+"'");
    500                 }
    501             }
    502         }
    503     }
    504 
    505     public Object load(AssetInfo info) throws IOException {
    506         this.assetManager = info.getManager();
    507 
    508         InputStream in = info.openStream();
    509         try {
    510             key = info.getKey();
    511             loadFromRoot(BlockLanguageParser.parse(in));
    512         } finally {
    513             if (in != null){
    514                 in.close();
    515             }
    516         }
    517 
    518         if (material != null){
    519             if (!(info.getKey() instanceof MaterialKey)){
    520                 throw new IOException("Material instances must be loaded via MaterialKey");
    521             }
    522             // material implementation
    523             return material;
    524         }else{
    525             // material definition
    526             return materialDef;
    527         }
    528     }
    529 
    530 }
    531