Browse Source

* New block language parser
* Rewrote J3M loader
* AssetManager.unregisterLocator() must be implemented
* Added support for material default vars
* Apply NVIDIA spot light fix for TerrainLighting

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8024 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

sha..rd 14 years ago
parent
commit
db0a4f23a8

+ 1 - 1
engine/src/core-data/Common/MatDefs/Gui/Gui.j3md

@@ -2,7 +2,7 @@ MaterialDef Default GUI {
 
     MaterialParameters {
         Texture2D Texture
-        Color Color : Color
+        Color Color ( Color )
         Boolean VertexColor
     }
 

+ 1 - 3
engine/src/core-data/Common/MatDefs/Light/Lighting.frag

@@ -2,7 +2,6 @@
 #define ATTENUATION
 //#define HQ_ATTENUATION
 
-
 varying vec2 texCoord;
 #ifdef SEPARATE_TEXCOORD
   varying vec2 texCoord2;
@@ -51,9 +50,8 @@ varying vec3 SpecularSum;
 #ifdef COLORRAMP
   uniform sampler2D m_ColorRamp;
 #endif
-uniform float m_AlphaDiscardThreshold;
-
 
+uniform float m_AlphaDiscardThreshold;
 
 #ifndef VERTEX_LIGHTING
 uniform float m_Shininess;

+ 4 - 4
engine/src/core-data/Common/MatDefs/Light/Lighting.j3md

@@ -38,16 +38,16 @@ MaterialDef Phong Lighting {
         Boolean UseVertexColor
 
         // Ambient color
-        Color Ambient : MaterialAmbient
+        Color Ambient (MaterialAmbient)
 
         // Diffuse color
-        Color Diffuse : MaterialDiffuse
+        Color Diffuse (MaterialDiffuse)
 
         // Specular color
-        Color Specular : MaterialSpecular
+        Color Specular (MaterialSpecular)
 
         // Specular power/shininess
-        Float Shininess : MaterialShininess
+        Float Shininess (MaterialShininess) : 1
 
         // Diffuse map
         Texture2D DiffuseMap

+ 1 - 1
engine/src/core-data/Common/MatDefs/Misc/Unshaded.j3md

@@ -3,7 +3,7 @@ MaterialDef Unshaded {
     MaterialParameters {
         Texture2D ColorMap
         Texture2D LightMap
-        Color Color : Color
+        Color Color ( Color )
         Boolean VertexColor
         Boolean SeparateTexCoord
 

+ 274 - 344
engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java

@@ -47,7 +47,6 @@ import com.jme3.material.RenderState.BlendMode;
 import com.jme3.material.RenderState.FaceCullMode;
 import com.jme3.material.TechniqueDef.LightMode;
 import com.jme3.material.TechniqueDef.ShadowMode;
-import com.jme3.shader.Shader.ShaderType;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;
@@ -57,13 +56,13 @@ import com.jme3.util.BufferUtils;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
-import java.util.Locale;
-import java.util.Scanner;
+import java.util.List;
+import com.jme3.util.blockparser.BlockLanguageParser;
+import com.jme3.util.blockparser.Statement;
 
 public class J3MLoader implements AssetLoader {
 
     private AssetManager owner;
-    private Scanner scan;
     private AssetKey key;
 
     private MaterialDef materialDef;
@@ -74,6 +73,8 @@ public class J3MLoader implements AssetLoader {
     private String shaderLang;
     private String vertName;
     private String fragName;
+    
+    private static final String whitespacePattern = "\\p{javaWhitespace}+";
 
     public J3MLoader(){
     }
@@ -86,29 +87,6 @@ public class J3MLoader implements AssetLoader {
             throw new IOException("Expected '"+expected+"', got '"+got+"'!");
     }
 
-    private void nextStatement(){
-        while (true){
-            if (scan.hasNext("\\}")){
-                break;
-            }else if (scan.hasNext("[\n;]")){
-                scan.next();
-            }else if (scan.hasNext("//")){
-                scan.useDelimiter("\n");
-                scan.next();
-                scan.useDelimiter("\\p{javaWhitespace}+");
-            }else{
-                break;
-            }
-        }
-    }
-
-    private String readString(String end){
-        scan.useDelimiter(end);
-        String str = scan.next();
-        scan.useDelimiter("\\p{javaWhitespace}+");
-        return str.trim();
-    }
-
     private Image createColorTexture(ColorRGBA color){
         if (color.getAlpha() == 1.0f){
             // create RGB texture
@@ -128,75 +106,49 @@ public class J3MLoader implements AssetLoader {
         }
     }
 
-    private void readShaderStatement(ShaderType type) throws IOException {
-        String lang = readString(":");
-
-        String word = scan.next();
-        throwIfNequal(":", word);
-
-        word = readString("[\n;(\\})]"); // new line, semicolon, comment or brace will end a statement
-        // locate source code
-
-        if (type == ShaderType.Vertex)
-            vertName = word;
-        else if (type == ShaderType.Fragment)
-            fragName = word;
-
-        shaderLang = lang;
+    // <TYPE> <LANG> : <SOURCE>
+    private void readShaderStatement(String statement) throws IOException {
+        String[] split = statement.split(":");
+        if (split.length != 2){
+            throw new IOException("Shader statement syntax incorrect" + statement);
+        }
+        String[] typeAndLang = split[0].split(whitespacePattern);
+        if (typeAndLang.length != 2){
+            throw new IOException("Shader statement syntax incorrect: " + statement);
+        }
+        shaderLang = typeAndLang[1];
+        if (typeAndLang[0].equals("VertexShader")){
+            vertName = split[1].trim();
+        }else if (typeAndLang[0].equals("FragmentShader")){
+            fragName = split[1].trim();
+        }
+        
     }
 
-    private void readLightMode(){
-        String mode = readString("[\n;(\\})]");
-        LightMode lm = LightMode.valueOf(mode);
+    // LightMode <MODE>
+    private void readLightMode(String statement) throws IOException{
+        String[] split = statement.split(whitespacePattern);
+        if (split.length != 2){
+            throw new IOException("LightMode statement syntax incorrect");
+        }
+        LightMode lm = LightMode.valueOf(split[1]);
         technique.setLightMode(lm);
     }
 
-    private void readShadowMode(){
-        String mode = readString("[\n;(\\})]");
-        ShadowMode sm = ShadowMode.valueOf(mode);
-        technique.setShadowMode(sm);
-    }
-
-    private void readParam() throws IOException{
-        String word = scan.next();
-        VarType type;
-        if (word.equals("Color")){
-            type = VarType.Vector4;
-        }else{
-            type = VarType.valueOf(word);
+    // ShadowMode <MODE>
+    private void readShadowMode(String statement) throws IOException{
+        String[] split = statement.split(whitespacePattern);
+        if (split.length != 2){
+            throw new IOException("ShadowMode statement syntax incorrect");
         }
-        
-        word = readString("[\n;(//)(\\})]");
-        FixedFuncBinding ffBinding = null;
-        if (word.contains(":")){
-            // contains fixed func binding
-            String[] split = word.split(":");
-            word = split[0].trim();
-
-            try {
-                ffBinding = FixedFuncBinding.valueOf(split[1].trim());
-            } catch (IllegalArgumentException ex){
-                throw new IOException("FixedFuncBinding '" +
-                                      split[1] + "' does not exist!");
-            }
-        }
-        // TODO: add support for default vals
-        materialDef.addMaterialParam(type, word, null, ffBinding);
+        ShadowMode sm = ShadowMode.valueOf(split[1]);
+        technique.setShadowMode(sm);
     }
 
-    private void readValueParam() throws IOException{
-        String name = readString(":");
-        throwIfNequal(":", scan.next());
-
-        // parse value
-        MatParam p = material.getMaterialDef().getMaterialParam(name);
-        if (p == null)
-            throw new IOException("The material parameter: "+name+" is undefined.");
-
-        VarType type = p.getVarType();
+    private Object readValue(VarType type, String value) throws IOException{
         if (type.isTextureType()){
 //            String texturePath = readString("[\n;(//)(\\})]");
-            String texturePath = readString("[\n;(\\})]");
+            String texturePath = value.trim();
             boolean flipY = false;
             boolean repeat = false;
             if (texturePath.startsWith("Flip Repeat ")){
@@ -219,98 +171,147 @@ public class J3MLoader implements AssetLoader {
             if (tex != null){
                 if (repeat)
                     tex.setWrap(WrapMode.Repeat);
-
-                material.setTextureParam(name, type, tex);
             }
+            return tex;
         }else{
+            String[] split = value.trim().split(whitespacePattern);
             switch (type){
                 case Float:
-                    material.setParam(name, type, scan.nextFloat());
-                    break;
+                    if (split.length != 1){
+                        throw new IOException("Float value parameter must have 1 entry: " + value);
+                    }
+                    return Float.parseFloat(split[0]);
                 case Vector2:
-                    material.setParam(name, type, new Vector2f(scan.nextFloat(),
-                                                               scan.nextFloat()));
-                    break;
+                    if (split.length != 2){
+                        throw new IOException("Vector2 value parameter must have 2 entries: " + value);
+                    }
+                    return new Vector2f(Float.parseFloat(split[0]),
+                                                               Float.parseFloat(split[1]));
                 case Vector3:
-                    material.setParam(name, type, new Vector3f(scan.nextFloat(),
-                                                               scan.nextFloat(),
-                                                               scan.nextFloat()));
-                    break;
+                    if (split.length != 3){
+                        throw new IOException("Vector3 value parameter must have 3 entries: " + value);
+                    }
+                    return new Vector3f(Float.parseFloat(split[0]),
+                                                               Float.parseFloat(split[1]),
+                                                               Float.parseFloat(split[2]));
                 case Vector4:
-                    material.setParam(name, type, new ColorRGBA(scan.nextFloat(),
-                                                                scan.nextFloat(),
-                                                                scan.nextFloat(),
-                                                                scan.nextFloat()));
-                    break;
+                    if (split.length != 4){
+                        throw new IOException("Vector4 value parameter must have 4 entries: " + value);
+                    }
+                    return new ColorRGBA(Float.parseFloat(split[0]),
+                                                                Float.parseFloat(split[1]),
+                                                                Float.parseFloat(split[2]),
+                                                                Float.parseFloat(split[3]));
                 case Int:
-                    material.setParam(name, type, scan.nextInt());
-                    break;
+                    if (split.length != 1){
+                        throw new IOException("Int value parameter must have 1 entry: " + value);
+                    }
+                    return Integer.parseInt(split[0]);
                 case Boolean:
-                    material.setParam(name, type, scan.nextBoolean());
-                    break;
+                    if (split.length != 1){
+                        throw new IOException("Boolean value parameter must have 1 entry: " + value);
+                    }
+                    return Boolean.parseBoolean(split[0]);
                 default:
                     throw new UnsupportedOperationException("Unknown type: "+type);
             }
         }
     }
-
-    private void readMaterialParams() throws IOException{
-        nextStatement();
-
-        String word = scan.next();
-        throwIfNequal("{", word);
-
-        nextStatement();
-
-        while (true){
-            if (scan.hasNext("\\}")){
-                scan.next();
-                break;
+    
+    // <TYPE> <NAME> [ "(" <FFBINDING> ")" ] [ ":" <DEFAULTVAL> ]
+    private void readParam(String statement) throws IOException{
+        String name;
+        String defaultVal = null;
+        FixedFuncBinding ffBinding = null;
+        
+        String[] split = statement.split(":");
+        
+        // Parse default val
+        if (split.length == 1){
+            // Doesn't contain default value
+        }else{
+            if (split.length != 2){
+                throw new IOException("Parameter statement syntax incorrect");
             }
-
-            readParam();
-            nextStatement();
+            statement = split[0].trim();
+            defaultVal = split[1].trim();
         }
+        
+        // Parse ffbinding
+        int startParen = statement.indexOf("(");
+        if (startParen != -1){
+            // get content inside parentheses
+            int endParen = statement.indexOf(")", startParen);
+            String bindingStr = statement.substring(startParen+1, endParen).trim();
+            try {
+                ffBinding = FixedFuncBinding.valueOf(bindingStr);
+            } catch (IllegalArgumentException ex){
+                throw new IOException("FixedFuncBinding '" +
+                                      split[1] + "' does not exist!");
+            }
+            statement = statement.substring(0, startParen);
+        }
+        
+        // Parse type + name
+        split = statement.split(whitespacePattern);
+        if (split.length != 2){
+            throw new IOException("Parameter statement syntax incorrect");
+        }
+        
+        VarType type;
+        if (split[0].equals("Color")){
+            type = VarType.Vector4;
+        }else{
+            type = VarType.valueOf(split[0]);
+        }
+        
+        name = split[1];
+        
+        Object defaultValObj = null;
+        if (defaultVal != null){ 
+            readValue(type, defaultVal);
+        }
+        
+        materialDef.addMaterialParam(type, name, defaultValObj, ffBinding);
     }
 
-    private void readExtendingMaterialParams() throws IOException{
-        nextStatement();
-
-        String word = scan.next();
-        throwIfNequal("{", word);
-
-        nextStatement();
+    private void readValueParam(String statement) throws IOException{
+        // Use limit=1 incase filename contains colons
+        String[] split = statement.split(":", 2);
+        if (split.length != 2){
+            throw new IOException("Value parameter statement syntax incorrect");
+        }
+        String name = split[0].trim();
 
-        while (true){
-            if (scan.hasNext("\\}")){
-                scan.next();
-                break;
-            }
+        // parse value
+        MatParam p = material.getMaterialDef().getMaterialParam(name);
+        if (p == null){
+            throw new IOException("The material parameter: "+name+" is undefined.");
+        }
 
-            readValueParam();
-            nextStatement();
+        Object valueObj = readValue(p.getVarType(), split[1]);
+        if (p.getVarType().isTextureType()){
+            material.setTextureParam(name, p.getVarType(), (Texture) valueObj);
+        }else{
+            material.setParam(name, p.getVarType(), valueObj);
         }
     }
 
-    private void readWorldParams() throws IOException{
-        nextStatement();
-
-        String word = scan.next();
-        throwIfNequal("{", word);
-
-        nextStatement();
+    private void readMaterialParams(List<Statement> paramsList) throws IOException{
+        for (Statement statement : paramsList){
+            readParam(statement.getLine());
+        }
+    }
 
-        while (true){
-            if (scan.hasNext("\\}")){
-                scan.next();
-                break;
-            }
+    private void readExtendingMaterialParams(List<Statement> paramsList) throws IOException{
+        for (Statement statement : paramsList){
+            readValueParam(statement.getLine());
+        }
+    }
 
-            word = readString("[\n;(//)(\\})]");
-            if (word != null && !word.equals("")){
-                technique.addWorldParam(word);
-            }
-            nextStatement();
+    private void readWorldParams(List<Statement> worldParams) throws IOException{
+        for (Statement statement : worldParams){
+            technique.addWorldParam(statement.getLine());
         }
     }
 
@@ -318,174 +319,111 @@ public class J3MLoader implements AssetLoader {
         return word != null && word.equals("On");
     }
 
-    private void readRenderStateStatement() throws IOException{
-        String word = scan.next();
-        if (word.equals("Wireframe")){
-            renderState.setWireframe(parseBoolean(scan.next()));
-        }else if (word.equals("FaceCull")){
-            renderState.setFaceCullMode(FaceCullMode.valueOf(scan.next()));
-        }else if (word.equals("DepthWrite")){
-            renderState.setDepthWrite(parseBoolean(scan.next()));
-        }else if (word.equals("DepthTest")){
-            renderState.setDepthTest(parseBoolean(scan.next()));
-        }else if (word.equals("Blend")){
-            renderState.setBlendMode(BlendMode.valueOf(scan.next()));
-        }else if (word.equals("AlphaTestFalloff")){
+    private void readRenderStateStatement(String statement) throws IOException{
+        String[] split = statement.split(whitespacePattern);
+        if (split[0].equals("Wireframe")){
+            renderState.setWireframe(parseBoolean(split[1]));
+        }else if (split[0].equals("FaceCull")){
+            renderState.setFaceCullMode(FaceCullMode.valueOf(split[1]));
+        }else if (split[0].equals("DepthWrite")){
+            renderState.setDepthWrite(parseBoolean(split[1]));
+        }else if (split[0].equals("DepthTest")){
+            renderState.setDepthTest(parseBoolean(split[1]));
+        }else if (split[0].equals("Blend")){
+            renderState.setBlendMode(BlendMode.valueOf(split[1]));
+        }else if (split[0].equals("AlphaTestFalloff")){
             renderState.setAlphaTest(true);
-            renderState.setAlphaFallOff(scan.nextFloat());
-        }else if (word.equals("PolyOffset")){
-            float factor = scan.nextFloat();
-            float units = scan.nextFloat();
+            renderState.setAlphaFallOff(Float.parseFloat(split[1]));
+        }else if (split[0].equals("PolyOffset")){
+            float factor = Float.parseFloat(split[1]);
+            float units = Float.parseFloat(split[2]);
             renderState.setPolyOffset(factor, units);
-        }else if (word.equals("ColorWrite")){
-            renderState.setColorWrite(parseBoolean(scan.next()));
-        }else if (word.equals("PointSprite")){
-            renderState.setPointSprite(parseBoolean(scan.next()));
+        }else if (split[0].equals("ColorWrite")){
+            renderState.setColorWrite(parseBoolean(split[1]));
+        }else if (split[0].equals("PointSprite")){
+            renderState.setPointSprite(parseBoolean(split[1]));
         }else{
-            throwIfNequal(null, word);
+            throwIfNequal(null, split[0]);
         }
     }
 
-    private void readAdditionalRenderState() throws IOException{
-        nextStatement();
-
-        String word = scan.next();
-        throwIfNequal("{", word);
-
-        nextStatement();
-
+    private void readAdditionalRenderState(List<Statement> renderStates) throws IOException{
         renderState = material.getAdditionalRenderState();
-
-        while (true){
-            if (scan.hasNext("\\}")){
-                scan.next();
-                break;
-            }
-
-            readRenderStateStatement();
-            nextStatement();
+        for (Statement statement : renderStates){
+            readRenderStateStatement(statement.getLine());
         }
-
         renderState = null;
     }
 
-    private void readRenderState() throws IOException{
-        nextStatement();
-
-        String word = scan.next();
-        throwIfNequal("{", word);
-
-        nextStatement();
-
+    private void readRenderState(List<Statement> renderStates) throws IOException{
         renderState = new RenderState();
-
-        while (true){
-            if (scan.hasNext("\\}")){
-                scan.next();
-                break;
-            }
-
-            readRenderStateStatement();
-            nextStatement();
+        for (Statement statement : renderStates){
+            readRenderStateStatement(statement.getLine());
         }
-        
         technique.setRenderState(renderState);
         renderState = null;
     }
     
-    private void readDefine(){
-        // stops at either next statement or colon
-        // ways to end a statement:
-        /*
-        Block {
-            Statement<new line>
-            Statement;
-            Statement //comment
-            Statement }
-        */
-        String defineName = readString("[\n;:(//)(\\})]");
-        if (defineName.equals(""))
-            return;
-
-        String matParamName = null;
-        if (scan.hasNext(":")){
-            scan.next();
-            // this time without colon
-            matParamName = readString("[\n;(//)(\\})]");
-            // add define <-> param mapping
-            technique.addShaderParamDefine(matParamName, defineName);
-        }else{
+    // <DEFINENAME> [ ":" <PARAMNAME> ]
+    private void readDefine(String statement) throws IOException{
+        String[] split = statement.split(":");
+        if (split.length == 1){
             // add preset define
-            technique.addShaderPresetDefine(defineName, VarType.Boolean, true);
+            technique.addShaderPresetDefine(split[0].trim(), VarType.Boolean, true);
+        }else if (split.length == 2){
+            technique.addShaderParamDefine(split[1].trim(), split[0].trim());
+        }else{
+            throw new IOException("Define syntax incorrect");
         }
     }
 
-    private void readDefines() throws IOException{
-        nextStatement();
-
-        String word = scan.next();
-        throwIfNequal("{", word);
-
-        nextStatement();
-
-        while (true){
-            if (scan.hasNext("\\}")){
-                scan.next();
-                break;
-            }
-
-            readDefine();
-            nextStatement();
+    private void readDefines(List<Statement> defineList) throws IOException{
+        for (Statement statement : defineList){
+            readDefine(statement.getLine());
         }
 
     }
 
-    private void readTechniqueStatement() throws IOException{
-        String word = scan.next();
-        if (word.equals("VertexShader")){
-            readShaderStatement(ShaderType.Vertex);
-        }else if (word.equals("FragmentShader")){
-            readShaderStatement(ShaderType.Fragment);
-        }else if (word.equals("LightMode")){
-            readLightMode();
-        }else if (word.equals("ShadowMode")){
-            readShadowMode();
-        }else if (word.equals("WorldParameters")){
-            readWorldParams();
-        }else if (word.equals("RenderState")){
-            readRenderState();
-        }else if (word.equals("Defines")){
-            readDefines();
+    private void readTechniqueStatement(Statement statement) throws IOException{
+        String[] split = statement.getLine().split("[ \\{]");
+        if (split[0].equals("VertexShader") ||
+            split[0].equals("FragmentShader")){
+            readShaderStatement(statement.getLine());
+        }else if (split[0].equals("LightMode")){
+            readLightMode(statement.getLine());
+        }else if (split[0].equals("ShadowMode")){
+            readShadowMode(statement.getLine());
+        }else if (split[0].equals("WorldParameters")){
+            readWorldParams(statement.getContents());
+        }else if (split[0].equals("RenderState")){
+            readRenderState(statement.getContents());
+        }else if (split[0].equals("Defines")){
+            readDefines(statement.getContents());
         }else{
-            throwIfNequal(null, word);
+            throwIfNequal(null, split[0]);
         }
-        nextStatement();
     }
 
-    private void readTransparentStatement() throws IOException{
-        String on = readString("[\n;(\\})]");
-        material.setTransparent(parseBoolean(on));
+    private void readTransparentStatement(String statement) throws IOException{
+        String[] split = statement.split(whitespacePattern);
+        if (split.length != 2){
+            throw new IOException("Transparent statement syntax incorrect");
+        }
+        material.setTransparent(parseBoolean(split[1]));
     }
 
-    private void readTechnique() throws IOException{
-        String name = null;
-        if (!scan.hasNext("\\{")){
-            name = scan.next();
+    private void readTechnique(Statement techStat) throws IOException{
+        String[] split = techStat.getLine().split(whitespacePattern);
+        if (split.length == 1){
+            technique = new TechniqueDef(null);
+        }else if (split.length == 2){
+            technique = new TechniqueDef(split[1]);
+        }else{
+            throw new IOException("Technique statement syntax incorrect");
         }
-        technique = new TechniqueDef(name);
-
-        String word = scan.next();
-        throwIfNequal("{", word);
-
-        nextStatement();
-
-        while (true){
-            if (scan.hasNext("\\}")){
-                scan.next();
-                break;
-            }
-
-            readTechniqueStatement();
+        
+        for (Statement statement : techStat.getContents()){
+            readTechniqueStatement(statement);
         }
 
         if (vertName != null && fragName != null){
@@ -499,40 +437,44 @@ public class J3MLoader implements AssetLoader {
         shaderLang = null;
     }
 
-    private void loadFromScanner() throws IOException{
-        nextStatement();
-
+    private void loadFromRoot(List<Statement> roots) throws IOException{
+        if (roots.size() == 2){
+            Statement exception = roots.get(0);
+            String line = exception.getLine();
+            if (line.startsWith("Exception")){
+                throw new AssetLoadException(line.substring("Exception ".length()));
+            }else{
+                throw new IOException("In multiroot material, expected first statement to be 'Exception'");
+            }
+        }else if (roots.size() != 1){
+            throw new IOException("Too many roots in J3M/J3MD file");
+        }
+        
         boolean extending = false;
-        String name = null;
-        String word = scan.next();
-        if (word.equals("Material")){
-            extending = true;
-        }else if (word.equals("MaterialDef")){
+        Statement materialStat = roots.get(0);
+        String materialName = materialStat.getLine();
+        if (materialName.startsWith("MaterialDef")){
+            materialName = materialName.substring("MaterialDef ".length()).trim();
             extending = false;
-        }else if (word.equals("Exception")){
-            String exception = scan.nextLine();
-            throw new AssetLoadException(exception);
+        }else if (materialName.startsWith("Material")){
+            materialName = materialName.substring("Material ".length()).trim();
+            extending = true;
         }else{
             throw new IOException("Specified file is not a Material file");
         }
-
-        nextStatement();
-
-        word = readString("[(\\{)(//)\n:]");
-        if (word == null || word.equals(""))
+        
+        String[] split = materialName.split(":", 2);
+        
+        if (materialName.equals("")){
             throw new IOException("Material name cannot be empty");
+        }
 
-        name = word;
-
-        nextStatement();
-
-        if (scan.hasNext(":")){
+        if (split.length == 2){
             if (!extending){
                 throw new IOException("Must use 'Material' when extending.");
             }
 
-            scan.next(); // skip colon
-            String extendedMat = readString("\\{");
+            String extendedMat = split[1].trim();
 
             MaterialDef def = (MaterialDef) owner.loadAsset(new AssetKey(extendedMat));
             if (def == null)
@@ -541,45 +483,35 @@ public class J3MLoader implements AssetLoader {
             material = new Material(def);
             material.setKey(key);
 //            material.setAssetName(fileName);
-        }else if (scan.hasNext("\\{")){
+        }else if (split.length == 1){
             if (extending){
                 throw new IOException("Expected ':', got '{'");
             }
-            materialDef = new MaterialDef(owner, name);
+            materialDef = new MaterialDef(owner, materialName);
             // NOTE: pass file name for defs so they can be loaded later
             materialDef.setAssetName(key.getName());
+        }else{
+            throw new IOException("Cannot use colon in material name/path");
         }
-        scan.next(); // skip {
-
-        nextStatement();
-
-        while (true){
-            if (scan.hasNext("\\}")){
-                scan.next();
-                break;
-            }
-
-            word = scan.next();
+        
+        for (Statement statement : materialStat.getContents()){
+            split = statement.getLine().split("[ \\{]");
+            String statType = split[0];
             if (extending){
-                if (word.equals("MaterialParameters")){
-                    readExtendingMaterialParams();
-                    nextStatement();
-                }else if (word.equals("AdditionalRenderState")){
-                    readAdditionalRenderState();
-                    nextStatement();
-                }else if (word.equals("Transparent")){
-                    readTransparentStatement();
-                    nextStatement();
+                if (statType.equals("MaterialParameters")){
+                    readExtendingMaterialParams(statement.getContents());
+                }else if (statType.equals("AdditionalRenderState")){
+                    readAdditionalRenderState(statement.getContents());
+                }else if (statType.equals("Transparent")){
+                    readTransparentStatement(statement.getLine());
                 }
             }else{
-                if (word.equals("Technique")){
-                    readTechnique();
-                    nextStatement();
-                }else if (word.equals("MaterialParameters")){
-                    readMaterialParams();
-                    nextStatement();
+                if (statType.equals("Technique")){
+                    readTechnique(statement);
+                }else if (statType.equals("MaterialParameters")){
+                    readMaterialParams(statement.getContents());
                 }else{
-                    throw new IOException("Expected material statement, got '"+scan.next()+"'");
+                    throw new IOException("Expected material statement, got '"+statType+"'");
                 }
             }
         }
@@ -590,10 +522,8 @@ public class J3MLoader implements AssetLoader {
 
         InputStream in = info.openStream();
         try {
-            scan = new Scanner(in);
-            scan.useLocale(Locale.US);
             key = info.getKey();
-            loadFromScanner();
+            loadFromRoot(BlockLanguageParser.parse(in));
         } finally {
             if (in != null)
                 in.close();

+ 13 - 0
engine/src/core/com/jme3/asset/AssetManager.java

@@ -79,6 +79,8 @@ public interface AssetManager {
      * they were registered, to locate the asset by the {@link AssetKey}.
      * Once an {@link AssetLocator} returns a non-null AssetInfo, it is sent
      * to the {@link AssetLoader} to load the asset.
+     * Once a locator is registered, it can be removed via
+     * {@link #unregisterLocator(java.lang.String, java.lang.Class) }.
      *
      * @param rootPath Specifies the root path from which to locate assets
      * for the given {@link AssetLocator}. The purpose of this parameter
@@ -87,9 +89,20 @@ public interface AssetManager {
      *
      * @see AssetLocator#setRootPath(java.lang.String)
      * @see AssetLocator#locate(com.jme3.asset.AssetManager, com.jme3.asset.AssetKey) 
+     * @see #unregisterLocator(java.lang.String, java.lang.Class) 
      */
     public void registerLocator(String rootPath, Class<? extends AssetLocator> locatorClass);
 
+    /**
+     * Unregisters the given locator class. This essentially undoes the operation
+     * done by {@link #registerLocator(java.lang.String, java.lang.Class) }.
+     * 
+     * @param rootPath Should be the same as the root path specified in {@link
+     * #registerLocator(java.lang.String, java.lang.Class) }.
+     * @param locatorClass The locator class to unregister
+     */
+    public void unregisterLocator(String rootPath, Class<? extends AssetLocator> locatorClass);
+    
     /**
      * Set an {@link AssetEventListener} to receive events from this
      * <code>AssetManager</code>. There can only be one {@link  AssetEventListener}

+ 2 - 1
engine/src/core/com/jme3/material/MatParam.java

@@ -128,7 +128,8 @@ public class MatParam implements Savable, Cloneable {
      * Returns the value of this material parameter.
      * <p>
      * Material parameters that are used for material definitions
-     * will not have a value.
+     * will not have a value, unless there's a default value declared
+     * in the definition.
      * 
      * @return the value of this material parameter.
      */

+ 7 - 2
engine/src/core/com/jme3/material/Material.java

@@ -53,8 +53,6 @@ import com.jme3.math.Vector4f;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
-import com.jme3.renderer.queue.RenderQueue.Bucket;
-import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.scene.Geometry;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Uniform;
@@ -115,6 +113,13 @@ public class Material implements Asset, Cloneable, Savable, Comparable<Material>
             throw new NullPointerException("Material definition cannot be null");
         }
         this.def = def;
+        
+        // Load default values from definition (if any)
+        for (MatParam param : def.getMaterialParams()){
+            if (param.getValue() != null){
+                setParam(param.getName(), param.getVarType(), param.getValue());
+            }
+        }
     }
 
     public Material(AssetManager contentMan, String defName) {

+ 14 - 0
engine/src/core/com/jme3/material/MaterialDef.java

@@ -35,6 +35,7 @@ package com.jme3.material;
 import com.jme3.asset.AssetManager;
 import com.jme3.shader.VarType;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -138,6 +139,19 @@ public class MaterialDef {
     public MatParam getMaterialParam(String name){
         return matParams.get(name);
     }
+    
+    /**
+     * Returns a collection of all material parameters declared in this
+     * material definition.
+     * <p>
+     * Modifying the material parameters or the collection will lead
+     * to undefined results.
+     * 
+     * @return All material parameters declared in this definition.
+     */
+    public Collection<MatParam> getMaterialParams(){
+        return matParams.values();
+    }
 
     /**
      * Adds a new technique definition to this material definition.

+ 92 - 0
engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java

@@ -0,0 +1,92 @@
+package com.jme3.util.blockparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BlockLanguageParser {
+    
+    private Reader reader;
+    private ArrayList<Statement> statementStack = new ArrayList<Statement>();
+    private Statement lastStatement;
+    private int lineNumber = 1;
+    
+    private BlockLanguageParser(){
+    }
+    
+    private void reset(){
+        statementStack.clear();
+        statementStack.add(new Statement(0, "<root>"));
+        lastStatement = null;
+        lineNumber = 1;
+    }
+    
+    private void pushStatement(StringBuilder buffer){
+        String content = buffer.toString().trim();
+        if (content.length() > 0){
+            // push last statement onto the list
+            lastStatement = new Statement(lineNumber, content);
+
+            Statement parent = statementStack.get(statementStack.size()-1);
+            parent.addStatement(lastStatement);
+
+            buffer.setLength(0);
+        }
+    }
+    
+    private void load(InputStream in) throws IOException{
+        reset();
+        
+        reader = new InputStreamReader(in);
+        
+        StringBuilder buffer = new StringBuilder();
+        boolean insideComment = false;
+        char lastChar = '\0';
+        
+        while (true){
+            int ci = reader.read();
+            char c = (char) ci;
+            if (c == '\r'){
+                continue;
+            }
+            if (insideComment && c == '\n'){
+                insideComment = false;
+            }else if (c == '/' && lastChar == '/'){
+                buffer.deleteCharAt(buffer.length()-1);
+                insideComment = true;
+                pushStatement(buffer);
+                lastChar = '\0';
+            }else if (!insideComment){
+                if (ci == -1 || c == '{' || c == '}' || c == '\n' || c == ';'){
+                    pushStatement(buffer);
+                    lastChar = '\0';
+                    if (c == '{'){
+                        // push last statement onto the stack
+                        statementStack.add(lastStatement);
+                        continue;
+                    }else if (c == '}'){
+                        // pop statement from stack
+                        statementStack.remove(statementStack.size()-1);
+                        continue;
+                    }else if (c == '\n'){
+                        lineNumber++;
+                    }else if (ci == -1){
+                        break;
+                    }
+                }else{
+                    buffer.append(c);
+                    lastChar = c;
+                }
+            }
+        }
+    }
+    
+    public static List<Statement> parse(InputStream in) throws IOException {
+        BlockLanguageParser parser = new BlockLanguageParser();
+        parser.load(in);
+        return parser.statementStack.get(0).getContents();
+    }
+}

+ 61 - 0
engine/src/core/com/jme3/util/blockparser/Statement.java

@@ -0,0 +1,61 @@
+package com.jme3.util.blockparser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Statement {
+    
+    private int lineNumber;
+    private String line;
+    private List<Statement> contents = new ArrayList<Statement>();
+
+    Statement(int lineNumber, String line) {
+        this.lineNumber = lineNumber;
+        this.line = line;
+    }
+    
+    void addStatement(Statement statement){
+//        if (contents == null){
+//            contents = new ArrayList<Statement>();
+//        }
+        contents.add(statement);
+    }
+
+    public int getLineNumber(){
+        return lineNumber;
+    }
+    
+    public String getLine() {
+        return line;
+    }
+
+    public List<Statement> getContents() {
+        return contents;
+    }
+    
+    private String getIndent(int indent){
+        return "                               ".substring(0, indent);
+    }
+    
+    private String toString(int indent){
+        StringBuilder sb = new StringBuilder();
+        sb.append(getIndent(indent));
+        sb.append(line);
+        if (contents != null){
+            sb.append(" {\n");
+            for (Statement statement : contents){
+                sb.append(statement.toString(indent+4));
+                sb.append("\n");
+            }
+            sb.append(getIndent(indent));
+            sb.append("}");
+        }
+        return sb.toString();
+    }
+    
+    @Override
+    public String toString(){
+        return toString(0);
+    }
+    
+}

+ 1 - 1
engine/src/desktop/com/jme3/asset/DesktopAssetManager.java

@@ -143,7 +143,7 @@ public class DesktopAssetManager implements AssetManager {
         }
     }
     
-    public void unregisterLocator(String rootPath, Class<?> clazz){
+    public void unregisterLocator(String rootPath, Class<? extends AssetLocator> clazz){
         handler.removeLocator(clazz, rootPath);
         if (logger.isLoggable(Level.FINER)){
             logger.log(Level.FINER, "Unregistered locator: {0}",

+ 1 - 1
engine/src/niftygui/Common/MatDefs/Nifty/Nifty.j3md

@@ -3,7 +3,7 @@ MaterialDef Default GUI {
     MaterialParameters {
         Texture2D Texture
         Boolean UseTex
-        Vector4 Color : Color
+        Vector4 Color (Color)
     }
 
     Technique {

+ 7 - 3
engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.frag

@@ -654,10 +654,14 @@ void main(){
               float innerAngleCos = floor(g_LightDirection.w) * 0.001;
               float outerAngleCos = fract(g_LightDirection.w);
               float innerMinusOuter = innerAngleCos - outerAngleCos;
-              spotFallOff = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0);
-              if(spotFallOff<=0.0){
-                  gl_FragColor =  AmbientSum * diffuseColor;
+
+              spotFallOff = (curAngleCos - outerAngleCos) / innerMinusOuter;
+
+              if(spotFallOff <= 0.0){
+                  gl_FragColor.rgb = AmbientSum * diffuseColor;
                   return;
+              }else{
+                  spotFallOff = clamp(spotFallOff, 0.0, 1.0);
               }
         }
     

+ 1 - 1
engine/src/test/jme3test/asset/TestAbsoluteLocators.java

@@ -44,7 +44,7 @@ public class TestAbsoluteLocators {
     public static void main(String[] args){
         AssetManager am = new DesktopAssetManager();
 
-        am.registerLoader(AWTLoader.class.getName(), "png");
+        am.registerLoader(AWTLoader.class.getName(), "jpg");
         am.registerLoader(WAVLoader.class.getName(), "wav");
 
         // register absolute locator

+ 1 - 1
engine/src/test/jme3test/light/TestSpotLightTerrain.java

@@ -156,7 +156,7 @@ public class TestSpotLightTerrain extends SimpleApplication {
         matTerrain.setFloat("DiffuseMap_3_scale", rockScale);
 
         // RIVER ROCK texture
-        Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.png");
+        Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg");
         riverRock.setWrap(WrapMode.Repeat);
         matTerrain.setTexture("DiffuseMap_4", riverRock);
         matTerrain.setFloat("DiffuseMap_4_scale", rockScale);