2
0
Эх сурвалжийг харах

Implementing value mappings for shader nodes. (#786)

* implemented value mappings for shader nodes.
Alexandr Brui 7 жил өмнө
parent
commit
a8c7a85fc1

+ 41 - 24
jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java

@@ -35,6 +35,7 @@ import com.jme3.asset.AssetManager;
 import com.jme3.material.ShaderGenerationInfo;
 import com.jme3.material.plugins.ConditionParser;
 import com.jme3.shader.Shader.ShaderType;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -50,6 +51,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
      * the indentation characters 1à tabulation characters
      */
     private final static String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
+
     protected ShaderNodeVariable inPosTmp;
 
     /**
@@ -99,7 +101,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
             declareAttribute(source, var);
 
         }
-        if (!inPosition) {            
+        if (!inPosition) {
             inPosTmp = new ShaderNodeVariable("vec3", "inPosition");
             declareAttribute(source, inPosTmp);
         }
@@ -246,7 +248,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
 
             //Variables fed with a sampler matparam or world param are replaced by the matparam itself
             //It avoids issue with samplers that have to be uniforms.
-            if (isWorldOrMaterialParam(rightVariable) && rightVariable.getType().startsWith("sampler")) {
+            if (rightVariable != null && isWorldOrMaterialParam(rightVariable) && rightVariable.getType().startsWith("sampler")) {
                 nodeSource = replace(nodeSource, leftVariable, rightVariable.getPrefix() + rightVariable.getName());
             } else {
 
@@ -415,56 +417,71 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
     }
 
     /**
-     * Appends a mapping to the source, embed in a conditional block if needed, 
+     * Appends a mapping to the source, embed in a conditional block if needed,
      * with variables nameSpaces and swizzle.
+     *
      * @param mapping the VariableMapping to append
-     * @param source the StringBuilder to use    
+     * @param source  the StringBuilder to use
      */
     protected void map(VariableMapping mapping, StringBuilder source) {
+
+        final ShaderNodeVariable leftVariable = mapping.getLeftVariable();
+        final ShaderNodeVariable rightVariable = mapping.getRightVariable();
+        final String rightExpression = mapping.getRightExpression();
+
         startCondition(mapping.getCondition(), source);
         appendIndent(source);
-        if (!mapping.getLeftVariable().isShaderOutput()) {
-            source.append(mapping.getLeftVariable().getType());
+        if (!leftVariable.isShaderOutput()) {
+            source.append(leftVariable.getType());
             source.append(" ");
         }
-        source.append(mapping.getLeftVariable().getNameSpace());
+        source.append(leftVariable.getNameSpace());
         source.append("_");
-        source.append(mapping.getLeftVariable().getName());
-        if (mapping.getLeftVariable().getMultiplicity() != null){
+        source.append(leftVariable.getName());
+        if (leftVariable.getMultiplicity() != null){
             source.append("[");
-            source.append(mapping.getLeftVariable().getMultiplicity());
+            source.append(leftVariable.getMultiplicity());
             source.append("]");
         }
-        
-        //left swizzle, the variable can't be declared and assigned on the same line. 
+
+        // left swizzle, the variable can't be declared and assigned on the same line.
         if (mapping.getLeftSwizzling().length() > 0) {
             //initialize the declared variable to 0.0
             source.append(" = ");
-            source.append(mapping.getLeftVariable().getType());
+            source.append(leftVariable.getType());
             source.append("(0.0);\n");
             appendIndent(source);
-            //assign the value on a new line
-            source.append(mapping.getLeftVariable().getNameSpace());
+            // assign the value on a new line
+            source.append(leftVariable.getNameSpace());
             source.append("_");
-            source.append(mapping.getLeftVariable().getName());
+            source.append(leftVariable.getName());
             source.append(".");
             source.append(mapping.getLeftSwizzling());
         }
         source.append(" = ");
-        String namePrefix = getAppendableNameSpace(mapping.getRightVariable());
-        source.append(namePrefix);
-        source.append(mapping.getRightVariable().getPrefix());
-        source.append(mapping.getRightVariable().getName());
-        if (mapping.getRightSwizzling().length() > 0) {
-            source.append(".");
-            source.append(mapping.getRightSwizzling());
+
+        if (rightVariable != null) {
+
+            String namePrefix = getAppendableNameSpace(rightVariable);
+            source.append(namePrefix);
+            source.append(rightVariable.getPrefix());
+            source.append(rightVariable.getName());
+
+            if (mapping.getRightSwizzling().length() > 0) {
+                source.append(".");
+                source.append(mapping.getRightSwizzling());
+            }
+
+        } else {
+            source.append(rightExpression);
         }
+
         source.append(";\n");
         endCondition(mapping.getCondition(), source);
     }
 
     /**
-     * replaces a variable name in a shaderNode source code by prefixing it 
+     * replaces a variable name in a shaderNode source code by prefixing it
      * with its nameSpace and "_" if needed.
      * @param nodeSource the source to modify
      * @param var the variable to replace

+ 5 - 0
jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java

@@ -50,6 +50,11 @@ import java.util.regex.Pattern;
  */
 public abstract class ShaderGenerator {
 
+    public static final String NAME_SPACE_GLOBAL = "Global";
+    public static final String NAME_SPACE_VERTEX_ATTRIBUTE = "Attr";
+    public static final String NAME_SPACE_MAT_PARAM = "MatParam";
+    public static final String NAME_SPACE_WORLD_PARAM = "WorldParam";
+
     /**
      * the asset manager
      */

+ 62 - 38
jme3-core/src/main/java/com/jme3/shader/ShaderNode.java

@@ -31,11 +31,8 @@
  */
 package com.jme3.shader;
 
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -59,15 +56,16 @@ public class ShaderNode implements Savable, Cloneable {
     private String name;
     private ShaderNodeDefinition definition;
     private String condition;
-    private List<VariableMapping> inputMapping = new ArrayList<VariableMapping>();
-    private List<VariableMapping> outputMapping = new ArrayList<VariableMapping>();
+
+    private List<VariableMapping> inputMapping = new ArrayList<>();
+    private List<VariableMapping> outputMapping = new ArrayList<>();
 
     /**
-     * creates a ShaderNode
+     * Creates a shader node.
      *
-     * @param name the name
-     * @param definition the ShaderNodeDefinition
-     * @param condition the condition to activate this node
+     * @param name       the name.
+     * @param definition the shader node definition.
+     * @param condition  the condition to activate this node.
      */
     public ShaderNode(String name, ShaderNodeDefinition definition, String condition) {
         this.name = name;
@@ -76,12 +74,13 @@ public class ShaderNode implements Savable, Cloneable {
     }
 
     /**
-     * creates a ShaderNode
+     * Creates a shader node.
      */
     public ShaderNode() {
     }
 
     /**
+     * Gets the name of the node.
      *
      * @return the name of the node
      */
@@ -90,82 +89,83 @@ public class ShaderNode implements Savable, Cloneable {
     }
 
     /**
-     * sets the name of th node
+     * Sets the name of the node.
      *
-     * @param name the name
+     * @param name the name of the node.
      */
     public void setName(String name) {
         this.name = name;
     }
 
     /**
-     * returns the definition
+     * Returns the shader node definition.
      *
-     * @return the ShaderNodeDefinition
+     * @return the shader node definition.
      */
     public ShaderNodeDefinition getDefinition() {
         return definition;
     }
 
     /**
-     * sets the definition
+     * Sets the shader node definition.
      *
-     * @param definition the ShaderNodeDefinition
+     * @param definition the shader node definition.
      */
     public void setDefinition(ShaderNodeDefinition definition) {
         this.definition = definition;
     }
 
     /**
+     * Gets the condition.
      *
-     * @return the condition
+     * @return the condition.
      */
     public String getCondition() {
         return condition;
     }
 
     /**
-     * sets the condition
+     * Sets the condition.
      *
-     * @param condition the condition
+     * @param condition the condition.
      */
     public void setCondition(String condition) {
         this.condition = condition;
     }
 
     /**
-     * return a list of VariableMapping representing the input mappings of this
-     * node
+     * Returns a list of variable mapping representing the input mappings of this
+     * node.
      *
-     * @return the input mappings
+     * @return the input mappings.
      */
     public List<VariableMapping> getInputMapping() {
         return inputMapping;
     }
 
     /**
-     * sets the input mappings
+     * Sets the input mappings.
      *
-     * @param inputMapping the input mappings
+     * @param inputMapping the input mappings.
      */
     public void setInputMapping(List<VariableMapping> inputMapping) {
         this.inputMapping = inputMapping;
     }
 
     /**
-     * return a list of VariableMapping representing the output mappings of this
-     * node
+     * Returns a list of variable mapping representing the output mappings of this
+     * node.
      *
-     * @return the output mappings
+     * @return the output mappings.
      */
     public List<VariableMapping> getOutputMapping() {
         return outputMapping;
     }
 
     /**
-     * sets the output mappings
+     * Sets the output mappings.
      *
-     * @param outputMapping the output mappings
+     * @param outputMapping the output mappings.
      */
     public void setOutputMapping(List<VariableMapping> outputMapping) {
         this.outputMapping = outputMapping;
@@ -179,7 +179,7 @@ public class ShaderNode implements Savable, Cloneable {
      */
     @Override
     public void write(JmeExporter ex) throws IOException {
-        OutputCapsule oc = (OutputCapsule) ex.getCapsule(this);
+        OutputCapsule oc = ex.getCapsule(this);
         oc.write(name, "name", "");
         oc.write(definition, "definition", null);
         oc.write(condition, "condition", null);
@@ -188,14 +188,14 @@ public class ShaderNode implements Savable, Cloneable {
     }
 
     /**
-     * jme serialization 
+     * jme serialization
      *
      * @param im the importer
      * @throws IOException
      */
     @Override
     public void read(JmeImporter im) throws IOException {
-        InputCapsule ic = (InputCapsule) im.getCapsule(this);
+        InputCapsule ic = im.getCapsule(this);
         name = ic.readString("name", "");
         definition = (ShaderNodeDefinition) ic.readSavable("definition", null);
         condition = ic.readString("condition", null);
@@ -210,24 +210,48 @@ public class ShaderNode implements Savable, Cloneable {
      */
     @Override
     public String toString() {
-        return "\nShaderNode{" + "\nname=" + name + ", \ndefinition=" + definition.getName() + ", \ncondition=" + condition + ", \ninputMapping=" + inputMapping + ", \noutputMapping=" + outputMapping + '}';
+
+        final StringBuilder builder = new StringBuilder("ShaderNode:");
+        builder.append("\n\tname=").append(name)
+                .append("\n\tdefinition=").append(definition.getName())
+                .append("\n\tcondition=").append(condition);
+
+        if (!inputMapping.isEmpty()) {
+            builder.append("\n\tinputMapping:\n");
+            for (final VariableMapping mapping : inputMapping) {
+                builder.append("\t\t").append(mapping).append('\n');
+            }
+        }
+
+        if (!outputMapping.isEmpty()) {
+            builder.append("\n\toutputMapping:\n");
+            for (final VariableMapping mapping : outputMapping) {
+                builder.append("\t\t").append(mapping).append('\n');
+            }
+        }
+
+        if (builder.charAt(builder.length() - 1) == '\n') {
+            builder.delete(builder.length() - 1, builder.length());
+        }
+
+        return builder.toString();
     }
 
     @Override
     public ShaderNode clone() throws CloneNotSupportedException {
         ShaderNode clone = (ShaderNode) super.clone();
 
-        //No need to clone the definition.
+        // No need to clone the definition.
         clone.definition = definition;
 
         clone.inputMapping = new ArrayList<>();
         for (VariableMapping variableMapping : inputMapping) {
-            clone.inputMapping.add((VariableMapping) variableMapping.clone());
+            clone.inputMapping.add(variableMapping.clone());
         }
 
         clone.outputMapping = new ArrayList<>();
         for (VariableMapping variableMapping : outputMapping) {
-            clone.outputMapping.add((VariableMapping) variableMapping.clone());
+            clone.outputMapping.add(variableMapping.clone());
         }
 
         return clone;

+ 1 - 1
jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java

@@ -299,7 +299,7 @@ public class ShaderNodeVariable implements Savable, Cloneable {
 
     @Override
     public String toString() {
-        return "\n" + type + ' ' + (nameSpace != null ? (nameSpace + '.') : "") + name;
+        return type + ' ' + (nameSpace != null ? (nameSpace + '.') : "") + name;
     }
 
     /**

+ 91 - 37
jme3-core/src/main/java/com/jme3/shader/VariableMapping.java

@@ -31,15 +31,13 @@
  */
 package com.jme3.shader;
 
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
+
 import java.io.IOException;
+import java.util.Objects;
 
 /**
- * represents a mapping between 2 ShaderNodeVariables
+ * Represents a mapping between 2 shader node variables or a left shader node variable and a value expression.
  *
  * @author Nehon
  */
@@ -47,68 +45,91 @@ public class VariableMapping implements Savable, Cloneable {
 
     private ShaderNodeVariable leftVariable;
     private ShaderNodeVariable rightVariable;
+    private String rightExpression;
     private String condition;
     private String leftSwizzling = "";
     private String rightSwizzling = "";
 
     /**
-     * creates a VariableMapping
+     * Creates a VariableMapping.
      */
     public VariableMapping() {
     }
 
     /**
-     * creates a VariableMapping
+     * Creates a VariableMapping.
      *
-     * @param leftVariable the left hand side variable of the expression
-     * @param leftSwizzling the swizzling of the left variable
-     * @param rightVariable the right hand side variable of the expression
+     * @param leftVariable   the left hand side variable of the expression
+     * @param leftSwizzling  the swizzling of the left variable
+     * @param rightVariable  the right hand side variable of the expression
      * @param rightSwizzling the swizzling of the right variable
-     * @param condition the condition for this mapping
+     * @param condition      the condition for this mapping
      */
-    public VariableMapping(ShaderNodeVariable leftVariable, String leftSwizzling, ShaderNodeVariable rightVariable, String rightSwizzling, String condition) {
+    public VariableMapping(ShaderNodeVariable leftVariable, String leftSwizzling, ShaderNodeVariable rightVariable,
+                           String rightSwizzling, String condition) {
         this.leftVariable = leftVariable;
         this.rightVariable = rightVariable;
         this.condition = condition;
-        this.leftSwizzling = leftSwizzling;
-        this.rightSwizzling = rightSwizzling;
+        this.leftSwizzling = Objects.requireNonNull(leftSwizzling);
+        this.rightSwizzling = Objects.requireNonNull(rightSwizzling);
     }
 
     /**
+     * Gets the left variable.
      *
-     * @return the left variable
+     * @return the left variable.
      */
     public ShaderNodeVariable getLeftVariable() {
         return leftVariable;
     }
 
     /**
-     * sets the left variable
+     * Sets the left variable.
      *
-     * @param leftVariable the left variable
+     * @param leftVariable the left variable.
      */
     public void setLeftVariable(ShaderNodeVariable leftVariable) {
         this.leftVariable = leftVariable;
     }
 
     /**
+     * Gets the right variable.
      *
-     * @return the right variable
+     * @return the right variable or null.
      */
     public ShaderNodeVariable getRightVariable() {
         return rightVariable;
     }
 
     /**
-     * sets the right variable
+     * Sets the right variable.
      *
-     * @param rightVariable the right variable
+     * @param rightVariable the right variable.
      */
     public void setRightVariable(ShaderNodeVariable rightVariable) {
         this.rightVariable = rightVariable;
     }
 
     /**
+     * Gets the right expression.
+     *
+     * @return the right expression or null.
+     */
+    public String getRightExpression() {
+        return rightExpression;
+    }
+
+    /**
+     * Sets the right expression.
+     *
+     * @param rightExpression the right expression.
+     */
+    public void setRightExpression(final String rightExpression) {
+        this.rightExpression = rightExpression;
+    }
+
+    /**
+     * Gets the condition.
      *
      * @return the condition
      */
@@ -117,46 +138,48 @@ public class VariableMapping implements Savable, Cloneable {
     }
 
     /**
-     * sets the condition
+     * Sets the condition.
      *
-     * @param condition the condition
+     * @param condition the condition or null.
      */
     public void setCondition(String condition) {
         this.condition = condition;
     }
 
     /**
+     * Gets the left swizzle.
      *
-     * @return the left swizzle
+     * @return the left swizzle or empty string.
      */
     public String getLeftSwizzling() {
         return leftSwizzling;
     }
 
     /**
-     * sets the left swizzle
+     * Sets the left swizzle.
      *
-     * @param leftSwizzling the left swizzle
+     * @param leftSwizzling the left swizzle.
      */
     public void setLeftSwizzling(String leftSwizzling) {
-        this.leftSwizzling = leftSwizzling;
+        this.leftSwizzling = Objects.requireNonNull(leftSwizzling);
     }
 
     /**
+     * Gets the right swizzle.
      *
-     * @return the right swizzle
+     * @return the right swizzle or empty string.
      */
     public String getRightSwizzling() {
         return rightSwizzling;
     }
 
     /**
-     * sets the right swizzle
+     * Sets the right swizzle.
      *
-     * @param rightSwizzling the right swizzle
+     * @param rightSwizzling the right swizzle.
      */
     public void setRightSwizzling(String rightSwizzling) {
-        this.rightSwizzling = rightSwizzling;
+        this.rightSwizzling = Objects.requireNonNull(rightSwizzling);
     }
 
     /**
@@ -167,9 +190,10 @@ public class VariableMapping implements Savable, Cloneable {
      */
     @Override
     public void write(JmeExporter ex) throws IOException {
-        OutputCapsule oc = (OutputCapsule) ex.getCapsule(this);
+        OutputCapsule oc = ex.getCapsule(this);
         oc.write(leftVariable, "leftVariable", null);
         oc.write(rightVariable, "rightVariable", null);
+        oc.write(rightExpression, "rightExpression", null);
         oc.write(condition, "condition", "");
         oc.write(leftSwizzling, "leftSwizzling", "");
         oc.write(rightSwizzling, "rightSwizzling", "");
@@ -183,9 +207,10 @@ public class VariableMapping implements Savable, Cloneable {
      */
     @Override
     public void read(JmeImporter im) throws IOException {
-        InputCapsule ic = (InputCapsule) im.getCapsule(this);
+        InputCapsule ic = im.getCapsule(this);
         leftVariable = (ShaderNodeVariable) ic.readSavable("leftVariable", null);
         rightVariable = (ShaderNodeVariable) ic.readSavable("rightVariable", null);
+        rightExpression = ic.readString("rightExpression", null);
         condition = ic.readString("condition", "");
         leftSwizzling = ic.readString("leftSwizzling", "");
         rightSwizzling = ic.readString("rightSwizzling", "");
@@ -193,16 +218,45 @@ public class VariableMapping implements Savable, Cloneable {
 
     @Override
     public String toString() {
-        return "\n{" + leftVariable.toString() + (leftSwizzling.length() > 0 ? ("." + leftSwizzling) : "") + " = " + rightVariable.getType() + " " + rightVariable.getNameSpace() + "." + rightVariable.getName() + (rightSwizzling.length() > 0 ? ("." + rightSwizzling) : "") + " : " + condition + "}";
+
+        final StringBuilder builder = new StringBuilder(leftVariable.toString());
+
+        if (!leftSwizzling.isEmpty()) {
+            builder.append('.').append(leftSwizzling);
+        }
+
+        builder.append(" = ");
+
+        if (rightVariable != null) {
+
+            builder.append(rightVariable.getType())
+                    .append(' ')
+                    .append(rightVariable.getNameSpace())
+                    .append('.')
+                    .append(rightVariable.getName());
+
+            if (!rightSwizzling.isEmpty()) {
+                builder.append('.').append(rightSwizzling);
+            }
+
+        } else if (rightExpression != null) {
+            builder.append(rightExpression);
+        }
+
+        if (condition != null && !condition.isEmpty()) {
+            builder.append(" : ").append(condition);
+        }
+
+        return builder.toString();
     }
 
     @Override
     protected VariableMapping clone() throws CloneNotSupportedException {
         VariableMapping clone = (VariableMapping) super.clone();
-
         clone.leftVariable = leftVariable.clone();
-        clone.rightVariable = rightVariable.clone();
-
+        if (rightVariable != null) {
+            clone.rightVariable = rightVariable.clone();
+        }
         return clone;
     }
 }

+ 233 - 159
jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java

@@ -38,15 +38,10 @@ import com.jme3.material.MatParam;
 import com.jme3.material.MaterialDef;
 import com.jme3.material.ShaderGenerationInfo;
 import com.jme3.material.TechniqueDef;
-import com.jme3.shader.Shader;
-import com.jme3.shader.ShaderNode;
-import com.jme3.shader.ShaderNodeDefinition;
-import com.jme3.shader.ShaderNodeVariable;
-import com.jme3.shader.ShaderUtils;
-import com.jme3.shader.UniformBinding;
-import com.jme3.shader.VarType;
-import com.jme3.shader.VariableMapping;
+import com.jme3.shader.Shader.ShaderType;
+import com.jme3.shader.*;
 import com.jme3.util.blockparser.Statement;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -65,6 +60,9 @@ import java.util.Map;
  */
 public class ShaderNodeLoaderDelegate {
 
+    private static final boolean[] IM_HAS_NAME_SPACE = {false, true};
+    private static final boolean[] OM_HAS_NAME_SPACE = {true, false};
+
     protected Map<String, ShaderNodeDefinition> nodeDefinitions;
     protected Map<String, ShaderNode> nodes;
     protected ShaderNodeDefinition shaderNodeDefinition;
@@ -165,7 +163,7 @@ public class ShaderNodeLoaderDelegate {
 
                 if (line.startsWith("Type")) {
                     String type = line.substring(line.lastIndexOf(':') + 1).trim();
-                    shaderNodeDefinition.setType(Shader.ShaderType.valueOf(type));
+                    shaderNodeDefinition.setType(ShaderType.valueOf(type));
                 } else if (line.startsWith("Shader ")) {
                     readShaderStatement(statement);
                     shaderNodeDefinition.getShadersLanguage().add(shaderLanguage);
@@ -267,61 +265,86 @@ public class ShaderNodeLoaderDelegate {
      * @throws IOException
      */
     protected void readShaderNode(List<Statement> statements) throws IOException {
+
+        final ShaderGenerationInfo generationInfo = techniqueDef.getShaderGenerationInfo();
+        final List<String> unusedNodes = generationInfo.getUnusedNodes();
+
         for (Statement statement : statements) {
+
             String line = statement.getLine();
             String[] split = statement.getLine().split("[ \\{]");
+
             if (line.startsWith("Definition")) {
                 ShaderNodeDefinition def = findDefinition(statement);
                 shaderNode.setDefinition(def);
                 if(def.isNoOutput()){
-                    techniqueDef.getShaderGenerationInfo().getUnusedNodes().remove(shaderNode.getName());
+                    unusedNodes.remove(shaderNode.getName());
                 }
             } else if (line.startsWith("Condition")) {
                 String condition = line.substring(line.lastIndexOf(":") + 1).trim();
                 extractCondition(condition, statement);
                 shaderNode.setCondition(conditionParser.getFormattedExpression());
-            } else if (line.startsWith("InputMapping")) {
-                for (Statement statement1 : statement.getContents()) {
-                    VariableMapping mapping = readInputMapping(statement1);
-                    techniqueDef.getShaderGenerationInfo().getUnusedNodes().remove(mapping.getRightVariable().getNameSpace());
+            } else if (line.startsWith("InputMappings")) {
+                for (final Statement subStatement : statement.getContents()) {
+
+                    VariableMapping mapping = readInputMapping(subStatement);
+
+                    final ShaderNodeVariable rightVariable = mapping.getRightVariable();
+                    if (rightVariable != null) {
+                        unusedNodes.remove(rightVariable.getNameSpace());
+                    }
+
                     shaderNode.getInputMapping().add(mapping);
                 }
-            } else if (line.startsWith("OutputMapping")) {
+            } else if (line.startsWith("OutputMappings")) {
                 for (Statement statement1 : statement.getContents()) {
                     VariableMapping mapping = readOutputMapping(statement1);
-                    techniqueDef.getShaderGenerationInfo().getUnusedNodes().remove(shaderNode.getName());
+                    unusedNodes.remove(shaderNode.getName());
                     shaderNode.getOutputMapping().add(mapping);
                 }
             } else {
                 throw new MatParseException("ShaderNodeDefinition", split[0], statement);
             }
         }
-
     }
 
     /**
-     * reads a mapping statement. Sets the nameSpace, name and swizzling of the
+     * Reads a mapping statement. Sets the nameSpace, name and swizzling of the
      * left variable. Sets the name, nameSpace and swizzling of the right
-     * variable types will be determined later.
-     *
-     * <code>
-     * Format : <nameSpace>.<varName>[.<swizzling>] =
-     * <nameSpace>.<varName>[.<swizzling>][:Condition]
-     * </code>
+     * variable types will be determined later. Also, we can have the right part as expression.
+     * <pre>
+     * Format variable to variable: &lt;nameSpace&gt;.&lt;varName&gt;[.&lt;swizzling&gt;] = &lt;nameSpace&gt;.&lt;varName&gt;[.&lt;swizzling&gt;][:Condition]
+     * Format expression to variable: &lt;nameSpace&gt;.&lt;varName&gt;[.&lt;swizzling&gt;] = %% expression %% [:Condition]
+     * </pre>
      *
-     * @param statement the statement to read
-     * @return the read mapping
+     * @param statement the statement to read.
+     * @return the read mapping.
+     * @throws MatParseException if the statement isn't valid.
      */
-    protected VariableMapping parseMapping(Statement statement, boolean[] hasNameSpace) throws IOException {
+    protected VariableMapping parseMapping(Statement statement, boolean[] hasNameSpace) throws MatParseException {
+
         VariableMapping mapping = new VariableMapping();
         String[] cond = statement.getLine().split(":");
-
         String[] vars = cond[0].split("=");
+
         checkMappingFormat(vars, statement);
+
         ShaderNodeVariable[] variables = new ShaderNodeVariable[2];
         String[] swizzle = new String[2];
+        String rightExpression = null;
+
         for (int i = 0; i < vars.length; i++) {
-            String[] expression = vars[i].trim().split("\\.");
+
+            final String var = vars[i].trim();
+
+            // it seems that is expression, not variable
+            if (var.contains("%%")) {
+                rightExpression = var.substring(2, var.length() - 2);
+                continue;
+            }
+
+            String[] expression = var.split("\\.");
+
             if (hasNameSpace[i]) {
                 if (expression.length <= 3) {
                     variables[i] = new ShaderNodeVariable("", expression[0].trim(), expression[1].trim());
@@ -337,13 +360,17 @@ public class ShaderNodeLoaderDelegate {
                     swizzle[i] = expression[1].trim();
                 }
             }
-
         }
 
         mapping.setLeftVariable(variables[0]);
         mapping.setLeftSwizzling(swizzle[0] != null ? swizzle[0] : "");
-        mapping.setRightVariable(variables[1]);
-        mapping.setRightSwizzling(swizzle[1] != null ? swizzle[1] : "");
+
+        if (rightExpression != null) {
+            mapping.setRightExpression(rightExpression);
+        } else {
+            mapping.setRightVariable(variables[1]);
+            mapping.setRightSwizzling(swizzle[1] != null ? swizzle[1] : "");
+        }
 
         if (cond.length > 1) {
             extractCondition(cond[1], statement);
@@ -401,11 +428,11 @@ public class ShaderNodeLoaderDelegate {
     }
 
     /**
-     * search a variable in the given list and updates its type and namespace
+     * Searches a variable in the given list and updates its type and namespace.
      *
-     * @param var the variable to update
-     * @param list the variables list
-     * @return true if the variable has been found and updated
+     * @param var  the variable to update.
+     * @param list the variables list.
+     * @return true if the variable has been found and updated.
      */
     protected boolean updateVariableFromList(ShaderNodeVariable var, List<ShaderNodeVariable> list) {
         for (ShaderNodeVariable shaderNodeVariable : list) {
@@ -420,10 +447,10 @@ public class ShaderNodeLoaderDelegate {
     }
 
     /**
-     * updates the type of the right variable of a mapping from the type of the
-     * left variable
+     * Updates the type of the right variable of a mapping from the type of the
+     * left variable.
      *
-     * @param mapping the mapping to consider
+     * @param mapping the mapping to consider.
      */
     protected void updateRightTypeFromLeftType(VariableMapping mapping) {
         String type = mapping.getLeftVariable().getType();
@@ -439,24 +466,25 @@ public class ShaderNodeLoaderDelegate {
     }
 
     /**
-     * check if once a mapping expression is split by "=" the resulting array
-     * have 2 elements
+     * Checks if once a mapping expression is split by "=" the resulting array
+     * have 2 elements.
      *
-     * @param vars the array
-     * @param statement the statement
-     * @throws IOException
+     * @param vars      the array.
+     * @param statement the statement.
+     * @throws MatParseException if the array isn't correct.
      */
-    protected void checkMappingFormat(String[] vars, Statement statement) throws IOException {
+    protected void checkMappingFormat(String[] vars, Statement statement) throws MatParseException {
         if (vars.length != 2) {
-            throw new MatParseException("Not a valid expression should be '<varName>[.<swizzling>] = <nameSpace>.<varName>[.<swizzling>][:Condition]'", statement);
+            throw new MatParseException("Not a valid expression should be '<varName>[.<swizzling>] = " +
+                    "<nameSpace>.<varName>[.<swizzling>][:Condition]'", statement);
         }
     }
 
     /**
-     * finds a MatParam in the materialDef from the given name
+     * Finds a {@link MatParam} in the {@link MaterialDef} from the given name.
      *
-     * @param varName the matparam name
-     * @return the MatParam
+     * @param varName the material param name.
+     * @return the found {@link MatParam} or null.
      */
     protected MatParam findMatParam(String varName) {
         for (MatParam matParam : materialDef.getMaterialParams()) {
@@ -493,6 +521,7 @@ public class ShaderNodeLoaderDelegate {
      * @return true if the param was added to the map
      */
     protected boolean updateRightFromUniforms(UniformBinding param, VariableMapping mapping, Map<String, DeclaredVariable> map) {
+
         ShaderNodeVariable right = mapping.getRightVariable();
         String name = param.toString();
 
@@ -513,60 +542,74 @@ public class ShaderNodeLoaderDelegate {
     }
 
     /**
-     * updates the right variable of the given mapping from a MatParam (a
+     * Updates the right variable of the given mapping from a {@link MatParam} (a
      * WorldParam) it checks if the uniform hasn't already been loaded, add it
      * to the maps if not.
      *
-     * @param param the MatParam
-     * @param mapping the mapping
-     * @param map the map of uniforms to search into
-     * @return true if the param was added to the map
+     * @param param   the mat param.
+     * @param mapping the mapping.
+     * @param map     the map of uniforms to search into.
+     * @return true if the param was added to the map.
      */
-    public boolean updateRightFromUniforms(MatParam param, VariableMapping mapping, Map<String, DeclaredVariable> map, Statement statement) throws MatParseException {
-        ShaderNodeVariable right = mapping.getRightVariable();
+    public boolean updateRightFromUniforms(MatParam param, VariableMapping mapping, Map<String, DeclaredVariable> map,
+                                           Statement statement) throws MatParseException {
+
+        final ShaderNodeVariable left = mapping.getLeftVariable();
+        final ShaderNodeVariable right = mapping.getRightVariable();
+
         DeclaredVariable dv = map.get(param.getName());
+
         if (dv == null) {
+
             right.setType(param.getVarType().getGlslType());
             right.setName(param.getName());
             right.setPrefix("m_");
-            if(mapping.getLeftVariable().getMultiplicity() != null){
-                if(!param.getVarType().name().endsWith("Array")){
+
+            if (left.getMultiplicity() != null) {
+
+                if (!param.getVarType().name().endsWith("Array")) {
                     throw new MatParseException(param.getName() + " is not of Array type", statement);
                 }
-                String multiplicity = mapping.getLeftVariable().getMultiplicity();
+
+                String multiplicity = left.getMultiplicity();
                 try {
                     Integer.parseInt(multiplicity);
-                } catch (NumberFormatException nfe) {
-                    //multiplicity is not an int attempting to find for a material parameter.
+                } catch (final NumberFormatException nfe) {
+                    // multiplicity is not an int attempting to find for a material parameter.
                     MatParam mp = findMatParam(multiplicity);
                     if (mp != null) {
-                        //It's tied to a material param, let's create a define and use this as the multiplicity
+                        // It's tied to a material param, let's create a define and use this as the multiplicity
                         addDefine(multiplicity, VarType.Int);
                         multiplicity = multiplicity.toUpperCase();
-                        mapping.getLeftVariable().setMultiplicity(multiplicity);
-                        //only declare the variable if the define is defined.
-                        mapping.getLeftVariable().setCondition(mergeConditions(mapping.getLeftVariable().getCondition(), "defined(" + multiplicity + ")", "||"));
+                        left.setMultiplicity(multiplicity);
+                        // only declare the variable if the define is defined.
+                        left.setCondition(mergeConditions(left.getCondition(), "defined(" + multiplicity + ")", "||"));
                     } else {
-                        throw new MatParseException("Wrong multiplicity for variable" + mapping.getLeftVariable().getName() + ". " + multiplicity + " should be an int or a declared material parameter.", statement);
+                        throw new MatParseException("Wrong multiplicity for variable" + left.getName() + ". " +
+                                multiplicity + " should be an int or a declared material parameter.", statement);
                     }
                 }
-                //the right variable must have the same multiplicity and the same condition.
+
+                // the right variable must have the same multiplicity and the same condition.
                 right.setMultiplicity(multiplicity);
-                right.setCondition(mapping.getLeftVariable().getCondition());
+                right.setCondition(left.getCondition());
             }
+
             dv = new DeclaredVariable(right);
             map.put(right.getName(), dv);
             dv.addNode(shaderNode);
             mapping.setRightVariable(right);
             return true;
         }
+
         dv.addNode(shaderNode);
         mapping.setRightVariable(dv.var);
+
         return false;
     }
 
     /**
-     * updates a variable from the Attribute list
+     * Updates a variable from the attribute list.
      *
      * @param right the variable
      * @param mapping the mapping
@@ -595,11 +638,11 @@ public class ShaderNodeLoaderDelegate {
     }
 
     /**
-     * find a variable with the given name from the list of variable
+     * Finds a variable with the given name from the list of variable.
      *
-     * @param vars a list of shaderNodeVariables
-     * @param rightVarName the variable name to search for
-     * @return the found variable or null is not found
+     * @param vars         the list of shader node variables.
+     * @param rightVarName the variable name to search for.
+     * @return the found variable or null is not found.
      */
     public ShaderNodeVariable findNodeOutput(List<ShaderNodeVariable> vars, String rightVarName) {
         ShaderNodeVariable var = null;
@@ -612,81 +655,95 @@ public class ShaderNodeLoaderDelegate {
     }
 
     /**
-     * extract and check a condition expression
+     * Extracts and checks a condition expression.
      *
-     * @param cond the condition expression
-     * @param statement the statement being read
-     * @throws IOException
+     * @param condition the condition expression.
+     * @param statement the statement being read.
+     * @throws MatParseException if the condition isn't valid.
      */
-    public void extractCondition(String cond, Statement statement) throws IOException {
-        List<String> defines = conditionParser.extractDefines(cond);
+    public void extractCondition(String condition, Statement statement) throws MatParseException {
+        List<String> defines = conditionParser.extractDefines(condition);
         for (String string : defines) {
             MatParam param = findMatParam(string);
             if (param != null) {
                 addDefine(param.getName(), param.getVarType());
             } else {
-                throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + cond, statement);
+                throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + condition, statement);
             }
         }
     }
 
     /**
-     * reads an input mapping
+     * Reads an input mapping.
      *
-     * @param statement1 the statement being read
-     * @return the mapping
-     * @throws IOException
+     * @param statement the statement being read.
+     * @return the variable mapping.
+     * @throws MatParseException if we have a problem with parsing input mapping statement.
      */
-    public VariableMapping readInputMapping(Statement statement1) throws IOException {
-        VariableMapping mapping = null;
+    public VariableMapping readInputMapping(Statement statement) throws MatParseException {
+
+        VariableMapping mapping;
         try {
-            mapping = parseMapping(statement1, new boolean[]{false, true});
-        } catch (Exception e) {
-            throw new MatParseException("Unexpected mapping format", statement1, e);
+            mapping = parseMapping(statement, IM_HAS_NAME_SPACE);
+        } catch (final Exception e) {
+            throw new MatParseException("Unexpected mapping format", statement, e);
         }
-        ShaderNodeVariable left = mapping.getLeftVariable();
-        ShaderNodeVariable right = mapping.getRightVariable();
-        if (!updateVariableFromList(left, shaderNode.getDefinition().getInputs())) {
-            throw new MatParseException(left.getName() + " is not an input variable of " + shaderNode.getDefinition().getName(), statement1);
+
+        final ShaderNodeDefinition definition = shaderNode.getDefinition();
+        final ShaderNodeVariable left = mapping.getLeftVariable();
+        final ShaderNodeVariable right = mapping.getRightVariable();
+        final String expression = mapping.getRightExpression();
+
+        if (!updateVariableFromList(left, definition.getInputs())) {
+            throw new MatParseException(left.getName() + " is not an input variable of " + definition.getName(), statement);
+        } else if (left.getType().startsWith("sampler") && (right == null || !right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_MAT_PARAM))) {
+            throw new MatParseException("Samplers can only be assigned to MatParams", statement);
         }
 
-        if (left.getType().startsWith("sampler") && !right.getNameSpace().equals("MatParam")) {
-            throw new MatParseException("Samplers can only be assigned to MatParams", statement1);
+        if (right == null && expression == null) {
+            throw new MatParseException("The mapping doesn't have a right variable or a right expression.", statement);
         }
 
-        if (right.getNameSpace().equals("Global")) {
-            right.setType("vec4");//Globals are all vec4 for now (maybe forever...)
-            storeGlobal(right, statement1);
+        if (right == null) {
+            return mapping;
+        }
 
-        } else if (right.getNameSpace().equals("Attr")) {
-            if (shaderNode.getDefinition().getType() == Shader.ShaderType.Fragment) {
-                throw new MatParseException("Cannot have an attribute as input in a fragment shader" + right.getName(), statement1);
+        if (right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_GLOBAL)) {
+            right.setType("vec4"); // Globals are all vec4 for now (maybe forever...)
+            storeGlobal(right, statement);
+        } else if (right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_VERTEX_ATTRIBUTE)) {
+            if (definition.getType() == ShaderType.Fragment) {
+                throw new MatParseException("Cannot have an attribute as input in a fragment shader" + right.getName(), statement);
             }
             updateVarFromAttributes(mapping.getRightVariable(), mapping);
             storeAttribute(mapping.getRightVariable());
-        } else if (right.getNameSpace().equals("MatParam")) {
+        } else if (right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_MAT_PARAM)) {
+
             MatParam param = findMatParam(right.getName());
             if (param == null) {
-                throw new MatParseException("Could not find a Material Parameter named " + right.getName(), statement1);
+                throw new MatParseException("Could not find a Material Parameter named " + right.getName(), statement);
             }
-            if (shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex) {
-                if (updateRightFromUniforms(param, mapping, vertexDeclaredUniforms, statement1)) {
-                    updateMaterialTextureType(statement1, mapping, left, param);
+
+            if (definition.getType() == ShaderType.Vertex) {
+                if (updateRightFromUniforms(param, mapping, vertexDeclaredUniforms, statement)) {
+                    updateMaterialTextureType(statement, mapping, left, param);
                     storeVertexUniform(mapping.getRightVariable());
                 }
             } else {
-                if (updateRightFromUniforms(param, mapping, fragmentDeclaredUniforms, statement1)) {
-                    updateMaterialTextureType(statement1, mapping, left, param);
+                if (updateRightFromUniforms(param, mapping, fragmentDeclaredUniforms, statement)) {
+                    updateMaterialTextureType(statement, mapping, left, param);
                     storeFragmentUniform(mapping.getRightVariable());
                 }
             }
 
-        } else if (right.getNameSpace().equals("WorldParam")) {
+        } else if (right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_WORLD_PARAM)) {
+
             UniformBinding worldParam = findWorldParam(right.getName());
             if (worldParam == null) {
-                throw new MatParseException("Could not find a World Parameter named " + right.getName(), statement1);
+                throw new MatParseException("Could not find a World Parameter named " + right.getName(), statement);
             }
-            if (shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex) {
+
+            if (definition.getType() == ShaderType.Vertex) {
                 if (updateRightFromUniforms(worldParam, mapping, vertexDeclaredUniforms)) {
                     storeVertexUniform(mapping.getRightVariable());
                 }
@@ -697,35 +754,43 @@ public class ShaderNodeLoaderDelegate {
             }
 
         } else {
+
             ShaderNode node = nodes.get(right.getNameSpace());
+
             if (node == null) {
-                throw new MatParseException("Undeclared node" + right.getNameSpace() + ". Make sure this node is declared before the current node", statement1);
+                throw new MatParseException("Undeclared node" + right.getNameSpace() +
+                        ". Make sure this node is declared before the current node", statement);
             }
+
             ShaderNodeVariable var = findNodeOutput(node.getDefinition().getOutputs(), right.getName());
+
             if (var == null) {
-                throw new MatParseException("Cannot find output variable" + right.getName() + " form ShaderNode " + node.getName(), statement1);
+                throw new MatParseException("Cannot find output variable" + right.getName() +
+                        " form ShaderNode " + node.getName(), statement);
             }
+
             right.setNameSpace(node.getName());
             right.setType(var.getType());
             right.setMultiplicity(var.getMultiplicity());
+
             mapping.setRightVariable(right);
-            storeVaryings(node, mapping.getRightVariable());
 
+            storeVaryings(node, mapping.getRightVariable());
         }
 
-        checkTypes(mapping, statement1);
+        checkTypes(mapping, statement);
 
         return mapping;
     }
 
     /**
-     * Updated the material texture type of the variable mapping.
+     * Updates the material texture type of the variable mapping.
      *
      * @param statement the statement.
      * @param mapping the variable mapping.
      * @param left the left variable.
      * @param param the material parameter.
-     * @throws MatParseException
+     * @throws MatParseException if the texture type isn't valid.
      */
     private void updateMaterialTextureType(final Statement statement, final VariableMapping mapping,
                                            final ShaderNodeVariable left, final MatParam param) throws MatParseException {
@@ -745,39 +810,41 @@ public class ShaderNodeLoaderDelegate {
     }
 
     /**
-     * reads an output mapping
+     * Reads an output mapping.
      *
-     * @param statement1 the statement being read
+     * @param statement the statement being read.
      * @return the mapping
-     * @throws IOException
+     * @throws MatParseException if we have a problem with parsing the statement.
      */
-    public VariableMapping readOutputMapping(Statement statement1) throws IOException {
-        VariableMapping mapping = null;
+    public VariableMapping readOutputMapping(Statement statement) throws MatParseException {
+
+        VariableMapping mapping;
         try {
-            mapping = parseMapping(statement1, new boolean[]{true, false});
-        } catch (Exception e) {
-            throw new MatParseException("Unexpected mapping format", statement1, e);
+            mapping = parseMapping(statement, OM_HAS_NAME_SPACE);
+        } catch (final Exception e) {
+            throw new MatParseException("Unexpected mapping format", statement, e);
         }
-        ShaderNodeVariable left = mapping.getLeftVariable();
-        ShaderNodeVariable right = mapping.getRightVariable();
 
+        final ShaderNodeDefinition definition = shaderNode.getDefinition();
+        final ShaderNodeVariable left = mapping.getLeftVariable();
+        final ShaderNodeVariable right = mapping.getRightVariable();
 
         if (left.getType().startsWith("sampler") || right.getType().startsWith("sampler")) {
-            throw new MatParseException("Samplers can only be inputs", statement1);
+            throw new MatParseException("Samplers can only be inputs", statement);
         }
 
-        if (left.getNameSpace().equals("Global")) {
-            left.setType("vec4");//Globals are all vec4 for now (maybe forever...)
-            storeGlobal(left, statement1);
+        if (left.getNameSpace().equals(ShaderGenerator.NAME_SPACE_GLOBAL)) {
+            left.setType("vec4"); // Globals are all vec4 for now (maybe forever...)
+            storeGlobal(left, statement);
         } else {
-            throw new MatParseException("Only Global nameSpace is allowed for outputMapping, got" + left.getNameSpace(), statement1);
+            throw new MatParseException("Only Global nameSpace is allowed for outputMapping, got" + left.getNameSpace(), statement);
         }
 
-        if (!updateVariableFromList(right, shaderNode.getDefinition().getOutputs())) {
-            throw new MatParseException(right.getName() + " is not an output variable of " + shaderNode.getDefinition().getName(), statement1);
+        if (!updateVariableFromList(right, definition.getOutputs())) {
+            throw new MatParseException(right.getName() + " is not an output variable of " + definition.getName(), statement);
         }
 
-        checkTypes(mapping, statement1);
+        checkTypes(mapping, statement);
 
         return mapping;
     }
@@ -805,7 +872,6 @@ public class ShaderNodeLoaderDelegate {
                     shaderNode = new ShaderNode();
                     shaderNode.setName(name);
                     techniqueDef.getShaderGenerationInfo().getUnusedNodes().add(name);
-
                     readShaderNode(statement.getContents());
                     nodes.put(name, shaderNode);
                     techniqueDef.getShaderNodes().add(shaderNode);
@@ -838,45 +904,54 @@ public class ShaderNodeLoaderDelegate {
     }
 
     /**
-     * stores a global output
+     * Stores a global output.
      *
-     * @param var the variable to store
-     * @param statement1 the statement being read
-     * @throws IOException
+     * @param var the variable to store.
+     * @param varStatement the statement being read.
+     * @throws MatParseException if we have duplicates of a global vertex output variable.
      */
-    public void storeGlobal(ShaderNodeVariable var, Statement statement1) throws IOException {
+    public void storeGlobal(ShaderNodeVariable var, Statement varStatement) throws MatParseException {
         var.setShaderOutput(true);
-        if (shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex) {
-            ShaderNodeVariable global = techniqueDef.getShaderGenerationInfo().getVertexGlobal();
+
+        final ShaderGenerationInfo generationInfo = techniqueDef.getShaderGenerationInfo();
+        final ShaderNodeDefinition definition = shaderNode.getDefinition();
+
+        if (definition.getType() == ShaderType.Vertex) {
+
+            ShaderNodeVariable global = generationInfo.getVertexGlobal();
+
             if (global != null) {
+
                 if (!global.getName().equals(var.getName())) {
-                    throw new MatParseException("A global output is already defined for the vertex shader: " + global.getName() + ". vertex shader can only have one global output", statement1);
+                    throw new MatParseException("A global output is already defined for the vertex shader: " +
+                            global.getName() + ". vertex shader can only have one global output", varStatement);
                 }
+
             } else {
-                techniqueDef.getShaderGenerationInfo().setVertexGlobal(var);
+                generationInfo.setVertexGlobal(var);
             }
-        } else if (shaderNode.getDefinition().getType() == Shader.ShaderType.Fragment) {
-            storeVariable(var, techniqueDef.getShaderGenerationInfo().getFragmentGlobals());
+
+        } else if (definition.getType() == ShaderType.Fragment) {
+            storeVariable(var, generationInfo.getFragmentGlobals());
         }
     }
 
     /**
-     * store an attribute
+     * Stores an attribute.
      *
-     * @param var the variable to store
+     * @param var the variable to store.
      */
     public void storeAttribute(ShaderNodeVariable var) {
         storeVariable(var, techniqueDef.getShaderGenerationInfo().getAttributes());
     }
 
     /**
-     * store a vertex uniform
+     * Stores a vertex uniform.
      *
-     * @param var the variable to store
+     * @param var the variable to store.
      */
     public void storeVertexUniform(ShaderNodeVariable var) {
         storeVariable(var, techniqueDef.getShaderGenerationInfo().getVertexUniforms());
-
     }
 
     /**
@@ -886,7 +961,6 @@ public class ShaderNodeLoaderDelegate {
      */
     public void storeFragmentUniform(ShaderNodeVariable var) {
         storeVariable(var, techniqueDef.getShaderGenerationInfo().getFragmentUniforms());
-
     }
 
     /**
@@ -959,8 +1033,8 @@ public class ShaderNodeLoaderDelegate {
         final ShaderNodeDefinition nodeDefinition = node.getDefinition();
         final ShaderNodeDefinition currentDefinition = shaderNode.getDefinition();
 
-        if (nodeDefinition.getType() != Shader.ShaderType.Vertex ||
-                currentDefinition.getType() != Shader.ShaderType.Fragment) {
+        if (nodeDefinition.getType() != ShaderType.Vertex ||
+                currentDefinition.getType() != ShaderType.Fragment) {
             return;
         }
 
@@ -1008,11 +1082,11 @@ public class ShaderNodeLoaderDelegate {
     }
 
     /**
-     * search a variable in a list from its name and merge the conditions of the
-     * variables
+     * Searches a variable in a list from its name and merges the conditions of the
+     * variables.
      *
-     * @param variable the variable
-     * @param varList the variable list
+     * @param variable the variable.
+     * @param varList the variable list.
      */
     public void storeVariable(ShaderNodeVariable variable, List<ShaderNodeVariable> varList) {
         for (ShaderNodeVariable var : varList) {

+ 64 - 29
jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdTechniqueDefWriter.java

@@ -5,14 +5,17 @@
  */
 package com.jme3.material.plugin.export.materialdef;
 
-import com.jme3.material.*;
+import com.jme3.material.MatParam;
+import com.jme3.material.RenderState;
+import com.jme3.material.TechniqueDef;
 import com.jme3.shader.*;
 
-import java.io.*;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.util.Collection;
-import java.util.regex.*;
-
-import static java.util.regex.Pattern.compile;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * @author nehon
@@ -168,47 +171,79 @@ public class J3mdTechniqueDefWriter {
         out.write(shaderNode.getDefinition().getPath());
         out.write("\n");
 
-        out.write("                InputMappings {\n");
-        for (VariableMapping mapping : shaderNode.getInputMapping()) {
-            writeVariableMapping(out, shaderNode, mapping, matParams);
-        }
-        out.write("                }\n");
+        final List<VariableMapping> inputMapping = shaderNode.getInputMapping();
+        final List<VariableMapping> outputMapping = shaderNode.getOutputMapping();
 
-        out.write("                OutputMappings {\n");
-        for (VariableMapping mapping : shaderNode.getOutputMapping()) {
-            writeVariableMapping(out, shaderNode, mapping, matParams);
+        if (!inputMapping.isEmpty()) {
+            out.write("                InputMappings {\n");
+            for (VariableMapping mapping : inputMapping) {
+                writeVariableMapping(out, shaderNode, mapping, matParams);
+            }
+            out.write("                }\n");
         }
-        out.write("                }\n");
 
+        if (!outputMapping.isEmpty()) {
+            out.write("                OutputMappings {\n");
+            for (VariableMapping mapping : outputMapping) {
+                writeVariableMapping(out, shaderNode, mapping, matParams);
+            }
+            out.write("                }\n");
+        }
 
         out.write("            }\n");
     }
 
-    private void writeVariableMapping(OutputStreamWriter out, ShaderNode shaderNode, VariableMapping mapping, Collection<MatParam> matParams) throws IOException {
+    private void writeVariableMapping(final OutputStreamWriter out, final ShaderNode shaderNode,
+                                      final VariableMapping mapping, final Collection<MatParam> matParams)
+            throws IOException {
+
+        final ShaderNodeVariable leftVar = mapping.getLeftVariable();
+        final ShaderNodeVariable rightVar = mapping.getRightVariable();
+        final String rightExpression = mapping.getRightExpression();
+
         out.write("                    ");
-        if(!mapping.getLeftVariable().getNameSpace().equals(shaderNode.getName())) {
-            out.write(mapping.getLeftVariable().getNameSpace());
+
+        if (!leftVar.getNameSpace().equals(shaderNode.getName())) {
+            out.write(leftVar.getNameSpace());
             out.write(".");
         }
-        out.write(mapping.getLeftVariable().getName());
-        if(!mapping.getLeftSwizzling().equals("")){
+
+        out.write(leftVar.getName());
+
+        if (!mapping.getLeftSwizzling().equals("")) {
             out.write(".");
             out.write(mapping.getLeftSwizzling());
         }
+
         out.write(" = ");
-        if(!mapping.getRightVariable().getNameSpace().equals(shaderNode.getName())) {
-            out.write(mapping.getRightVariable().getNameSpace());
-            out.write(".");
-        }
-        out.write(mapping.getRightVariable().getName().replaceFirst("g_","").replaceFirst("m_",""));
-        if(!mapping.getRightSwizzling().equals("")){
-            out.write(".");
-            out.write(mapping.getRightSwizzling());
+
+        if (rightVar != null) {
+
+            if (!rightVar.getNameSpace().equals(shaderNode.getName())) {
+                out.write(rightVar.getNameSpace());
+                out.write(".");
+            }
+
+            String rightVarName = rightVar.getName();
+            if (rightVarName.startsWith("g_") || rightVarName.startsWith("m_")) {
+                rightVarName = rightVarName.substring(2, rightVarName.length());
+            }
+
+            out.write(rightVarName);
+
+            if (!mapping.getRightSwizzling().equals("")) {
+                out.write(".");
+                out.write(mapping.getRightSwizzling());
+            }
+        } else {
+            out.write("%%");
+            out.write(rightExpression);
+            out.write("%%");
         }
 
-        if (mapping.getCondition() != null){
+        if (mapping.getCondition() != null) {
             out.write(" : ");
-            out.write(formatCondition(mapping.getCondition(),matParams));
+            out.write(formatCondition(mapping.getCondition(), matParams));
         }
 
         out.write("\n");