• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
J3MLoader()75     public J3MLoader(){
76     }
77 
throwIfNequal(String expected, String got)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>
readShaderStatement(String statement)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>
readLightMode(String statement)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>
readShadowMode(String statement)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 
readValue(VarType type, String value)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> ]
readParam(String statement)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 
readValueParam(String statement)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 
readMaterialParams(List<Statement> paramsList)285     private void readMaterialParams(List<Statement> paramsList) throws IOException{
286         for (Statement statement : paramsList){
287             readParam(statement.getLine());
288         }
289     }
290 
readExtendingMaterialParams(List<Statement> paramsList)291     private void readExtendingMaterialParams(List<Statement> paramsList) throws IOException{
292         for (Statement statement : paramsList){
293             readValueParam(statement.getLine());
294         }
295     }
296 
readWorldParams(List<Statement> worldParams)297     private void readWorldParams(List<Statement> worldParams) throws IOException{
298         for (Statement statement : worldParams){
299             technique.addWorldParam(statement.getLine());
300         }
301     }
302 
parseBoolean(String word)303     private boolean parseBoolean(String word){
304         return word != null && word.equals("On");
305     }
306 
readRenderStateStatement(String statement)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 
readAdditionalRenderState(List<Statement> renderStates)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 
readRenderState(List<Statement> renderStates)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> ]
readDefine(String statement)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 
readDefines(List<Statement> defineList)365     private void readDefines(List<Statement> defineList) throws IOException{
366         for (Statement statement : defineList){
367             readDefine(statement.getLine());
368         }
369 
370     }
371 
readTechniqueStatement(Statement statement)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 
readTransparentStatement(String statement)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 
readTechnique(Statement techStat)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 
loadFromRoot(List<Statement> roots)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 
load(AssetInfo info)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