From a8c7a85fc18704dbe6e8b0d1b90816a7adfa9b58 Mon Sep 17 00:00:00 2001 From: Alexandr Brui Date: Tue, 6 Feb 2018 21:36:14 +0300 Subject: [PATCH] Implementing value mappings for shader nodes. (#786) * implemented value mappings for shader nodes. --- .../jme3/shader/Glsl100ShaderGenerator.java | 65 +-- .../java/com/jme3/shader/ShaderGenerator.java | 5 + .../main/java/com/jme3/shader/ShaderNode.java | 100 +++-- .../com/jme3/shader/ShaderNodeVariable.java | 2 +- .../java/com/jme3/shader/VariableMapping.java | 128 ++++-- .../plugins/ShaderNodeLoaderDelegate.java | 392 +++++++++++------- .../materialdef/J3mdTechniqueDefWriter.java | 93 +++-- 7 files changed, 497 insertions(+), 288 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java index 0aa8eeee7..9673880b1 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java +++ b/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 diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java index 6782b617d..a86340ab9 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java +++ b/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 */ diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java index 4f5779363..5ef500ffd 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java +++ b/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 inputMapping = new ArrayList(); - private List outputMapping = new ArrayList(); + + private List inputMapping = new ArrayList<>(); + private List 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 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 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 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 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; diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java index 6b4240d02..5038efb70 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java +++ b/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; } /** diff --git a/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java b/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java index 4a88c5d9f..a9a1e2366 100644 --- a/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java +++ b/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; } } diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java index 9dc620460..9eecd12b7 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java +++ b/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 nodeDefinitions; protected Map 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 statements) throws IOException { + + final ShaderGenerationInfo generationInfo = techniqueDef.getShaderGenerationInfo(); + final List 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. - * - * - * Format : .[.] = - * .[.][:Condition] - * + * variable types will be determined later. Also, we can have the right part as expression. + *
+     * Format variable to variable: <nameSpace>.<varName>[.<swizzling>] = <nameSpace>.<varName>[.<swizzling>][:Condition]
+     * Format expression to variable: <nameSpace>.<varName>[.<swizzling>] = %% expression %% [:Condition]
+     * 
* - * @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 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 '[.] = .[.][:Condition]'", statement); + throw new MatParseException("Not a valid expression should be '[.] = " + + ".[.][: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 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 map, Statement statement) throws MatParseException { - ShaderNodeVariable right = mapping.getRightVariable(); + public boolean updateRightFromUniforms(MatParam param, VariableMapping mapping, Map 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 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 defines = conditionParser.extractDefines(cond); + public void extractCondition(String condition, Statement statement) throws MatParseException { + List 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 varList) { for (ShaderNodeVariable var : varList) { diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdTechniqueDefWriter.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdTechniqueDefWriter.java index e2e3098cc..eabfda1c0 100644 --- a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdTechniqueDefWriter.java +++ b/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 inputMapping = shaderNode.getInputMapping(); + final List 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 matParams) throws IOException { + private void writeVariableMapping(final OutputStreamWriter out, final ShaderNode shaderNode, + final VariableMapping mapping, final Collection 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");