Home | History | Annotate | Download | only in material
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
      3  * <p/>
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are met:
      6  *
      7  * * Redistributions of source code must retain the above copyright notice,
      8  * this list of conditions and the following disclaimer.
      9  * <p/>
     10  * * Redistributions in binary form must reproduce the above copyright
     11  * notice, this list of conditions and the following disclaimer in the
     12  * documentation and/or other materials provided with the distribution.
     13  * <p/>
     14  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     15  * may be used to endorse or promote products derived from this software
     16  * without specific prior written permission.
     17  * <p/>
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     28  * POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 package com.jme3.material;
     31 
     32 import com.jme3.asset.Asset;
     33 import com.jme3.asset.AssetKey;
     34 import com.jme3.asset.AssetManager;
     35 import com.jme3.export.*;
     36 import com.jme3.light.*;
     37 import com.jme3.material.RenderState.BlendMode;
     38 import com.jme3.material.RenderState.FaceCullMode;
     39 import com.jme3.material.TechniqueDef.LightMode;
     40 import com.jme3.material.TechniqueDef.ShadowMode;
     41 import com.jme3.math.*;
     42 import com.jme3.renderer.Caps;
     43 import com.jme3.renderer.RenderManager;
     44 import com.jme3.renderer.Renderer;
     45 import com.jme3.renderer.queue.RenderQueue.Bucket;
     46 import com.jme3.scene.Geometry;
     47 import com.jme3.shader.Shader;
     48 import com.jme3.shader.Uniform;
     49 import com.jme3.shader.VarType;
     50 import com.jme3.texture.Texture;
     51 import com.jme3.util.ListMap;
     52 import com.jme3.util.TempVars;
     53 import java.io.IOException;
     54 import java.util.*;
     55 import java.util.logging.Level;
     56 import java.util.logging.Logger;
     57 
     58 /**
     59  * <code>Material</code> describes the rendering style for a given
     60  * {@link Geometry}.
     61  * <p>A material is essentially a list of {@link MatParam parameters},
     62  * those parameters map to uniforms which are defined in a shader.
     63  * Setting the parameters can modify the behavior of a
     64  * shader.
     65  * <p/>
     66  * @author Kirill Vainer
     67  */
     68 public class Material implements Asset, Cloneable, Savable, Comparable<Material> {
     69 
     70     // Version #2: Fixed issue with RenderState.apply*** flags not getting exported
     71     public static final int SAVABLE_VERSION = 2;
     72 
     73     private static final Logger logger = Logger.getLogger(Material.class.getName());
     74     private static final RenderState additiveLight = new RenderState();
     75     private static final RenderState depthOnly = new RenderState();
     76     private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1);
     77 
     78     static {
     79         depthOnly.setDepthTest(true);
     80         depthOnly.setDepthWrite(true);
     81         depthOnly.setFaceCullMode(RenderState.FaceCullMode.Back);
     82         depthOnly.setColorWrite(false);
     83 
     84         additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive);
     85         additiveLight.setDepthWrite(false);
     86     }
     87     private AssetKey key;
     88     private String name;
     89     private MaterialDef def;
     90     private ListMap<String, MatParam> paramValues = new ListMap<String, MatParam>();
     91     private Technique technique;
     92     private HashMap<String, Technique> techniques = new HashMap<String, Technique>();
     93     private int nextTexUnit = 0;
     94     private RenderState additionalState = null;
     95     private RenderState mergedRenderState = new RenderState();
     96     private boolean transparent = false;
     97     private boolean receivesShadows = false;
     98     private int sortingId = -1;
     99     private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
    100 
    101     public Material(MaterialDef def) {
    102         if (def == null) {
    103             throw new NullPointerException("Material definition cannot be null");
    104         }
    105         this.def = def;
    106 
    107         // Load default values from definition (if any)
    108         for (MatParam param : def.getMaterialParams()){
    109             if (param.getValue() != null){
    110                 setParam(param.getName(), param.getVarType(), param.getValue());
    111             }
    112         }
    113     }
    114 
    115     public Material(AssetManager contentMan, String defName) {
    116         this((MaterialDef) contentMan.loadAsset(new AssetKey(defName)));
    117     }
    118 
    119     /**
    120      * Do not use this constructor. Serialization purposes only.
    121      */
    122     public Material() {
    123     }
    124 
    125     /**
    126      * Returns the asset key name of the asset from which this material was loaded.
    127      *
    128      * <p>This value will be <code>null</code> unless this material was loaded
    129      * from a .j3m file.
    130      *
    131      * @return Asset key name of the j3m file
    132      */
    133     public String getAssetName() {
    134         return key != null ? key.getName() : null;
    135     }
    136 
    137     /**
    138      * @return the name of the material (not the same as the asset name), the returned value can be null
    139      */
    140     public String getName() {
    141 		return name;
    142 	}
    143 
    144     /**
    145      * This method sets the name of the material.
    146      * The name is not the same as the asset name.
    147      * It can be null and there is no guarantee of its uniqness.
    148      * @param name the name of the material
    149      */
    150     public void setName(String name) {
    151 		this.name = name;
    152 	}
    153 
    154     public void setKey(AssetKey key) {
    155         this.key = key;
    156     }
    157 
    158     public AssetKey getKey() {
    159         return key;
    160     }
    161 
    162     /**
    163      * Returns the sorting ID or sorting index for this material.
    164      *
    165      * <p>The sorting ID is used internally by the system to sort rendering
    166      * of geometries. It sorted to reduce shader switches, if the shaders
    167      * are equal, then it is sorted by textures.
    168      *
    169      * @return The sorting ID used for sorting geometries for rendering.
    170      */
    171     public int getSortId() {
    172         Technique t = getActiveTechnique();
    173         if (sortingId == -1 && t != null && t.getShader() != null) {
    174             int texId = -1;
    175             for (int i = 0; i < paramValues.size(); i++) {
    176                 MatParam param = paramValues.getValue(i);
    177                 if (param instanceof MatParamTexture) {
    178                     MatParamTexture tex = (MatParamTexture) param;
    179                     if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) {
    180                         if (texId == -1) {
    181                             texId = 0;
    182                         }
    183                         texId += tex.getTextureValue().getImage().getId() % 0xff;
    184                     }
    185                 }
    186             }
    187             sortingId = texId + t.getShader().getId() * 1000;
    188         }
    189         return sortingId;
    190     }
    191 
    192     /**
    193      * Uses the sorting ID for each material to compare them.
    194      *
    195      * @param m The other material to compare to.
    196      *
    197      * @return zero if the materials are equal, returns a negative value
    198      * if <code>this</code> has a lower sorting ID than <code>m</code>,
    199      * otherwise returns a positive value.
    200      */
    201     public int compareTo(Material m) {
    202         return m.getSortId() - getSortId();
    203     }
    204 
    205     @Override
    206     public boolean equals(Object obj) {
    207         if(obj instanceof Material){
    208             return ((Material)obj).compareTo(this) == 0;
    209         }
    210         return super.equals(obj);
    211     }
    212 
    213     /**
    214      * Clones this material. The result is returned.
    215      */
    216     @Override
    217     public Material clone() {
    218         try {
    219             Material mat = (Material) super.clone();
    220 
    221             if (additionalState != null) {
    222                 mat.additionalState = additionalState.clone();
    223             }
    224             mat.technique = null;
    225             mat.techniques = new HashMap<String, Technique>();
    226 
    227             mat.paramValues = new ListMap<String, MatParam>();
    228             for (int i = 0; i < paramValues.size(); i++) {
    229                 Map.Entry<String, MatParam> entry = paramValues.getEntry(i);
    230                 mat.paramValues.put(entry.getKey(), entry.getValue().clone());
    231             }
    232 
    233             return mat;
    234         } catch (CloneNotSupportedException ex) {
    235             throw new AssertionError();
    236         }
    237     }
    238 
    239     /**
    240      * Returns the currently active technique.
    241      * <p>
    242      * The technique is selected automatically by the {@link RenderManager}
    243      * based on system capabilities. Users may select their own
    244      * technique by using
    245      * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }.
    246      *
    247      * @return the currently active technique.
    248      *
    249      * @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager)
    250      */
    251     public Technique getActiveTechnique() {
    252         return technique;
    253     }
    254 
    255     /**
    256      * Check if the transparent value marker is set on this material.
    257      * @return True if the transparent value marker is set on this material.
    258      * @see #setTransparent(boolean)
    259      */
    260     public boolean isTransparent() {
    261         return transparent;
    262     }
    263 
    264     /**
    265      * Set the transparent value marker.
    266      *
    267      * <p>This value is merely a marker, by itself it does nothing.
    268      * Generally model loaders will use this marker to indicate further
    269      * up that the material is transparent and therefore any geometries
    270      * using it should be put into the {@link Bucket#Transparent transparent
    271      * bucket}.
    272      *
    273      * @param transparent the transparent value marker.
    274      */
    275     public void setTransparent(boolean transparent) {
    276         this.transparent = transparent;
    277     }
    278 
    279     /**
    280      * Check if the material should receive shadows or not.
    281      *
    282      * @return True if the material should receive shadows.
    283      *
    284      * @see Material#setReceivesShadows(boolean)
    285      */
    286     public boolean isReceivesShadows() {
    287         return receivesShadows;
    288     }
    289 
    290     /**
    291      * Set if the material should receive shadows or not.
    292      *
    293      * <p>This value is merely a marker, by itself it does nothing.
    294      * Generally model loaders will use this marker to indicate
    295      * the material should receive shadows and therefore any
    296      * geometries using it should have the {@link ShadowMode#Receive} set
    297      * on them.
    298      *
    299      * @param receivesShadows if the material should receive shadows or not.
    300      */
    301     public void setReceivesShadows(boolean receivesShadows) {
    302         this.receivesShadows = receivesShadows;
    303     }
    304 
    305     /**
    306      * Acquire the additional {@link RenderState render state} to apply
    307      * for this material.
    308      *
    309      * <p>The first call to this method will create an additional render
    310      * state which can be modified by the user to apply any render
    311      * states in addition to the ones used by the renderer. Only render
    312      * states which are modified in the additional render state will be applied.
    313      *
    314      * @return The additional render state.
    315      */
    316     public RenderState getAdditionalRenderState() {
    317         if (additionalState == null) {
    318             additionalState = RenderState.ADDITIONAL.clone();
    319         }
    320         return additionalState;
    321     }
    322 
    323     /**
    324      * Get the material definition (j3md file info) that <code>this</code>
    325      * material is implementing.
    326      *
    327      * @return the material definition this material implements.
    328      */
    329     public MaterialDef getMaterialDef() {
    330         return def;
    331     }
    332 
    333     /**
    334      * Returns the parameter set on this material with the given name,
    335      * returns <code>null</code> if the parameter is not set.
    336      *
    337      * @param name The parameter name to look up.
    338      * @return The MatParam if set, or null if not set.
    339      */
    340     public MatParam getParam(String name) {
    341         MatParam param = paramValues.get(name);
    342         return param;
    343     }
    344 
    345     /**
    346      * Returns the texture parameter set on this material with the given name,
    347      * returns <code>null</code> if the parameter is not set.
    348      *
    349      * @param name The parameter name to look up.
    350      * @return The MatParamTexture if set, or null if not set.
    351      */
    352     public MatParamTexture getTextureParam(String name) {
    353         MatParam param = paramValues.get(name);
    354         if (param instanceof MatParamTexture) {
    355             return (MatParamTexture) param;
    356         }
    357         return null;
    358     }
    359 
    360     /**
    361      * Returns a collection of all parameters set on this material.
    362      *
    363      * @return a collection of all parameters set on this material.
    364      *
    365      * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
    366      */
    367     public Collection<MatParam> getParams() {
    368         return paramValues.values();
    369     }
    370 
    371     private String checkSetParam(VarType type, String name) {
    372         MatParam paramDef = def.getMaterialParam(name);
    373         String newName = name;
    374 
    375         if (paramDef == null && name.startsWith("m_")) {
    376             newName = name.substring(2);
    377             paramDef = def.getMaterialParam(newName);
    378             if (paramDef == null) {
    379                 throw new IllegalArgumentException("Material parameter is not defined: " + name);
    380             } else {
    381                 logger.log(Level.WARNING, "Material parameter {0} uses a deprecated naming convention use {1} instead ", new Object[]{name, newName});
    382             }
    383         } else if (paramDef == null) {
    384             throw new IllegalArgumentException("Material parameter is not defined: " + name);
    385         }
    386 
    387         if (type != null && paramDef.getVarType() != type) {
    388             logger.log(Level.WARNING, "Material parameter being set: {0} with "
    389                     + "type {1} doesn''t match definition types {2}", new Object[]{name, type.name(), paramDef.getVarType()} );
    390         }
    391 
    392         return newName;
    393     }
    394 
    395     /**
    396      * Pass a parameter to the material shader.
    397      *
    398      * @param name the name of the parameter defined in the material definition (j3md)
    399      * @param type the type of the parameter {@link VarType}
    400      * @param value the value of the parameter
    401      */
    402     public void setParam(String name, VarType type, Object value) {
    403         name = checkSetParam(type, name);
    404 
    405         MatParam val = getParam(name);
    406         if (technique != null) {
    407             technique.notifySetParam(name, type, value);
    408         }
    409         if (val == null) {
    410             MatParam paramDef = def.getMaterialParam(name);
    411             paramValues.put(name, new MatParam(type, name, value, paramDef.getFixedFuncBinding()));
    412         } else {
    413             val.setValue(value);
    414         }
    415     }
    416 
    417     /**
    418      * Clear a parameter from this material. The parameter must exist
    419      * @param name the name of the parameter to clear
    420      */
    421     public void clearParam(String name) {
    422         //On removal, we don't check if the param exists in the paramDef, and just go on with the process.
    423         // name = checkSetParam(null, name);
    424 
    425         MatParam matParam = getParam(name);
    426         if (matParam != null) {
    427             paramValues.remove(name);
    428             if (technique != null) {
    429                 technique.notifyClearParam(name);
    430             }
    431             if (matParam instanceof MatParamTexture) {
    432                 int texUnit = ((MatParamTexture) matParam).getUnit();
    433                 nextTexUnit--;
    434                 for (MatParam param : paramValues.values()) {
    435                     if (param instanceof MatParamTexture) {
    436                         MatParamTexture texParam = (MatParamTexture) param;
    437                         if (texParam.getUnit() > texUnit) {
    438                             texParam.setUnit(texParam.getUnit() - 1);
    439                         }
    440                     }
    441                 }
    442             }
    443         }
    444 //        else {
    445 //            throw new IllegalArgumentException("The given parameter is not set.");
    446 //        }
    447     }
    448 
    449     private void clearTextureParam(String name) {
    450         name = checkSetParam(null, name);
    451 
    452         MatParamTexture val = getTextureParam(name);
    453         if (val == null) {
    454             throw new IllegalArgumentException("The given texture for parameter \"" + name + "\" is null.");
    455         }
    456 
    457         int texUnit = val.getUnit();
    458         paramValues.remove(name);
    459         nextTexUnit--;
    460         for (MatParam param : paramValues.values()) {
    461             if (param instanceof MatParamTexture) {
    462                 MatParamTexture texParam = (MatParamTexture) param;
    463                 if (texParam.getUnit() > texUnit) {
    464                     texParam.setUnit(texParam.getUnit() - 1);
    465                 }
    466             }
    467         }
    468 
    469         sortingId = -1;
    470     }
    471 
    472     /**
    473      * Set a texture parameter.
    474      *
    475      * @param name The name of the parameter
    476      * @param type The variable type {@link VarType}
    477      * @param value The texture value of the parameter.
    478      *
    479      * @throws IllegalArgumentException is value is null
    480      */
    481     public void setTextureParam(String name, VarType type, Texture value) {
    482         if (value == null) {
    483             throw new IllegalArgumentException();
    484         }
    485 
    486         name = checkSetParam(type, name);
    487         MatParamTexture val = getTextureParam(name);
    488         if (val == null) {
    489             paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++));
    490         } else {
    491             val.setTextureValue(value);
    492         }
    493 
    494         if (technique != null) {
    495             technique.notifySetParam(name, type, nextTexUnit - 1);
    496         }
    497 
    498         // need to recompute sort ID
    499         sortingId = -1;
    500     }
    501 
    502     /**
    503      * Pass a texture to the material shader.
    504      *
    505      * @param name the name of the texture defined in the material definition
    506      * (j3md) (for example Texture for Lighting.j3md)
    507      * @param value the Texture object previously loaded by the asset manager
    508      */
    509     public void setTexture(String name, Texture value) {
    510         if (value == null) {
    511             // clear it
    512             clearTextureParam(name);
    513             return;
    514         }
    515 
    516         VarType paramType = null;
    517         switch (value.getType()) {
    518             case TwoDimensional:
    519                 paramType = VarType.Texture2D;
    520                 break;
    521             case TwoDimensionalArray:
    522                 paramType = VarType.TextureArray;
    523                 break;
    524             case ThreeDimensional:
    525                 paramType = VarType.Texture3D;
    526                 break;
    527             case CubeMap:
    528                 paramType = VarType.TextureCubeMap;
    529                 break;
    530             default:
    531                 throw new UnsupportedOperationException("Unknown texture type: " + value.getType());
    532         }
    533 
    534         setTextureParam(name, paramType, value);
    535     }
    536 
    537     /**
    538      * Pass a Matrix4f to the material shader.
    539      *
    540      * @param name the name of the matrix defined in the material definition (j3md)
    541      * @param value the Matrix4f object
    542      */
    543     public void setMatrix4(String name, Matrix4f value) {
    544         setParam(name, VarType.Matrix4, value);
    545     }
    546 
    547     /**
    548      * Pass a boolean to the material shader.
    549      *
    550      * @param name the name of the boolean defined in the material definition (j3md)
    551      * @param value the boolean value
    552      */
    553     public void setBoolean(String name, boolean value) {
    554         setParam(name, VarType.Boolean, value);
    555     }
    556 
    557     /**
    558      * Pass a float to the material shader.
    559      *
    560      * @param name the name of the float defined in the material definition (j3md)
    561      * @param value the float value
    562      */
    563     public void setFloat(String name, float value) {
    564         setParam(name, VarType.Float, value);
    565     }
    566 
    567     /**
    568      * Pass an int to the material shader.
    569      *
    570      * @param name the name of the int defined in the material definition (j3md)
    571      * @param value the int value
    572      */
    573     public void setInt(String name, int value) {
    574         setParam(name, VarType.Int, value);
    575     }
    576 
    577     /**
    578      * Pass a Color to the material shader.
    579      *
    580      * @param name the name of the color defined in the material definition (j3md)
    581      * @param value the ColorRGBA value
    582      */
    583     public void setColor(String name, ColorRGBA value) {
    584         setParam(name, VarType.Vector4, value);
    585     }
    586 
    587     /**
    588      * Pass a Vector2f to the material shader.
    589      *
    590      * @param name the name of the Vector2f defined in the material definition (j3md)
    591      * @param value the Vector2f value
    592      */
    593     public void setVector2(String name, Vector2f value) {
    594         setParam(name, VarType.Vector2, value);
    595     }
    596 
    597     /**
    598      * Pass a Vector3f to the material shader.
    599      *
    600      * @param name the name of the Vector3f defined in the material definition (j3md)
    601      * @param value the Vector3f value
    602      */
    603     public void setVector3(String name, Vector3f value) {
    604         setParam(name, VarType.Vector3, value);
    605     }
    606 
    607     /**
    608      * Pass a Vector4f to the material shader.
    609      *
    610      * @param name the name of the Vector4f defined in the material definition (j3md)
    611      * @param value the Vector4f value
    612      */
    613     public void setVector4(String name, Vector4f value) {
    614         setParam(name, VarType.Vector4, value);
    615     }
    616 
    617     private ColorRGBA getAmbientColor(LightList lightList) {
    618         ambientLightColor.set(0, 0, 0, 1);
    619         for (int j = 0; j < lightList.size(); j++) {
    620             Light l = lightList.get(j);
    621             if (l instanceof AmbientLight) {
    622                 ambientLightColor.addLocal(l.getColor());
    623             }
    624         }
    625         ambientLightColor.a = 1.0f;
    626         return ambientLightColor;
    627     }
    628 
    629     /**
    630      * Uploads the lights in the light list as two uniform arrays.<br/><br/>
    631      *      * <p>
    632      * <code>uniform vec4 g_LightColor[numLights];</code><br/>
    633      * // g_LightColor.rgb is the diffuse/specular color of the light.<br/>
    634      * // g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/>
    635      * // 2 = Spot. <br/>
    636      * <br/>
    637      * <code>uniform vec4 g_LightPosition[numLights];</code><br/>
    638      * // g_LightPosition.xyz is the position of the light (for point lights)<br/>
    639      * // or the direction of the light (for directional lights).<br/>
    640      * // g_LightPosition.w is the inverse radius (1/r) of the light (for attenuation) <br/>
    641      * </p>
    642      */
    643     protected void updateLightListUniforms(Shader shader, Geometry g, int numLights) {
    644         if (numLights == 0) { // this shader does not do lighting, ignore.
    645             return;
    646         }
    647 
    648         LightList lightList = g.getWorldLightList();
    649         Uniform lightColor = shader.getUniform("g_LightColor");
    650         Uniform lightPos = shader.getUniform("g_LightPosition");
    651         Uniform lightDir = shader.getUniform("g_LightDirection");
    652         lightColor.setVector4Length(numLights);
    653         lightPos.setVector4Length(numLights);
    654         lightDir.setVector4Length(numLights);
    655 
    656         Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
    657         ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
    658 
    659         int lightIndex = 0;
    660 
    661         for (int i = 0; i < numLights; i++) {
    662             if (lightList.size() <= i) {
    663                 lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
    664                 lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
    665             } else {
    666                 Light l = lightList.get(i);
    667                 ColorRGBA color = l.getColor();
    668                 lightColor.setVector4InArray(color.getRed(),
    669                         color.getGreen(),
    670                         color.getBlue(),
    671                         l.getType().getId(),
    672                         i);
    673 
    674                 switch (l.getType()) {
    675                     case Directional:
    676                         DirectionalLight dl = (DirectionalLight) l;
    677                         Vector3f dir = dl.getDirection();
    678                         lightPos.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightIndex);
    679                         break;
    680                     case Point:
    681                         PointLight pl = (PointLight) l;
    682                         Vector3f pos = pl.getPosition();
    683                         float invRadius = pl.getInvRadius();
    684                         lightPos.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightIndex);
    685                         break;
    686                     case Spot:
    687                         SpotLight sl = (SpotLight) l;
    688                         Vector3f pos2 = sl.getPosition();
    689                         Vector3f dir2 = sl.getDirection();
    690                         float invRange = sl.getInvSpotRange();
    691                         float spotAngleCos = sl.getPackedAngleCos();
    692 
    693                         lightPos.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightIndex);
    694                         lightDir.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightIndex);
    695                         break;
    696                     case Ambient:
    697                         // skip this light. Does not increase lightIndex
    698                         continue;
    699                     default:
    700                         throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
    701                 }
    702             }
    703 
    704             lightIndex++;
    705         }
    706 
    707         while (lightIndex < numLights) {
    708             lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
    709             lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
    710 
    711             lightIndex++;
    712         }
    713     }
    714 
    715     protected void renderMultipassLighting(Shader shader, Geometry g, RenderManager rm) {
    716 
    717         Renderer r = rm.getRenderer();
    718         LightList lightList = g.getWorldLightList();
    719         Uniform lightDir = shader.getUniform("g_LightDirection");
    720         Uniform lightColor = shader.getUniform("g_LightColor");
    721         Uniform lightPos = shader.getUniform("g_LightPosition");
    722         Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
    723         boolean isFirstLight = true;
    724         boolean isSecondLight = false;
    725 
    726         for (int i = 0; i < lightList.size(); i++) {
    727             Light l = lightList.get(i);
    728             if (l instanceof AmbientLight) {
    729                 continue;
    730             }
    731 
    732             if (isFirstLight) {
    733                 // set ambient color for first light only
    734                 ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
    735                 isFirstLight = false;
    736                 isSecondLight = true;
    737             } else if (isSecondLight) {
    738                 ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
    739                 // apply additive blending for 2nd and future lights
    740                 r.applyRenderState(additiveLight);
    741                 isSecondLight = false;
    742             }
    743 
    744             TempVars vars = TempVars.get();
    745             Quaternion tmpLightDirection = vars.quat1;
    746             Quaternion tmpLightPosition = vars.quat2;
    747             ColorRGBA tmpLightColor = vars.color;
    748             Vector4f tmpVec = vars.vect4f;
    749 
    750             ColorRGBA color = l.getColor();
    751             tmpLightColor.set(color);
    752             tmpLightColor.a = l.getType().getId();
    753             lightColor.setValue(VarType.Vector4, tmpLightColor);
    754 
    755             switch (l.getType()) {
    756                 case Directional:
    757                     DirectionalLight dl = (DirectionalLight) l;
    758                     Vector3f dir = dl.getDirection();
    759 
    760                     tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
    761                     lightPos.setValue(VarType.Vector4, tmpLightPosition);
    762                     tmpLightDirection.set(0, 0, 0, 0);
    763                     lightDir.setValue(VarType.Vector4, tmpLightDirection);
    764                     break;
    765                 case Point:
    766                     PointLight pl = (PointLight) l;
    767                     Vector3f pos = pl.getPosition();
    768                     float invRadius = pl.getInvRadius();
    769 
    770                     tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
    771                     lightPos.setValue(VarType.Vector4, tmpLightPosition);
    772                     tmpLightDirection.set(0, 0, 0, 0);
    773                     lightDir.setValue(VarType.Vector4, tmpLightDirection);
    774                     break;
    775                 case Spot:
    776                     SpotLight sl = (SpotLight) l;
    777                     Vector3f pos2 = sl.getPosition();
    778                     Vector3f dir2 = sl.getDirection();
    779                     float invRange = sl.getInvSpotRange();
    780                     float spotAngleCos = sl.getPackedAngleCos();
    781 
    782                     tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
    783                     lightPos.setValue(VarType.Vector4, tmpLightPosition);
    784 
    785                     //We transform the spot directoin in view space here to save 5 varying later in the lighting shader
    786                     //one vec4 less and a vec4 that becomes a vec3
    787                     //the downside is that spotAngleCos decoding happen now in the frag shader.
    788                     tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),0);
    789                     rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
    790                     tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
    791 
    792                     lightDir.setValue(VarType.Vector4, tmpLightDirection);
    793 
    794                     break;
    795                 default:
    796                     throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
    797             }
    798             vars.release();
    799             r.setShader(shader);
    800             r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
    801         }
    802 
    803         if (isFirstLight && lightList.size() > 0) {
    804             // There are only ambient lights in the scene. Render
    805             // a dummy "normal light" so we can see the ambient
    806             ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
    807             lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
    808             lightPos.setValue(VarType.Vector4, nullDirLight);
    809             r.setShader(shader);
    810             r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
    811         }
    812     }
    813 
    814     /**
    815      * Select the technique to use for rendering this material.
    816      * <p>
    817      * If <code>name</code> is "Default", then one of the
    818      * {@link MaterialDef#getDefaultTechniques() default techniques}
    819      * on the material will be selected. Otherwise, the named technique
    820      * will be found in the material definition.
    821      * <p>
    822      * Any candidate technique for selection (either default or named)
    823      * must be verified to be compatible with the system, for that, the
    824      * <code>renderManager</code> is queried for capabilities.
    825      *
    826      * @param name The name of the technique to select, pass "Default" to
    827      * select one of the default techniques.
    828      * @param renderManager The {@link RenderManager render manager}
    829      * to query for capabilities.
    830      *
    831      * @throws IllegalArgumentException If "Default" is passed and no default
    832      * techniques are available on the material definition, or if a name
    833      * is passed but there's no technique by that name.
    834      * @throws UnsupportedOperationException If no candidate technique supports
    835      * the system capabilities.
    836      */
    837     public void selectTechnique(String name, RenderManager renderManager) {
    838         // check if already created
    839         Technique tech = techniques.get(name);
    840         if (tech == null) {
    841             // When choosing technique, we choose one that
    842             // supports all the caps.
    843             EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
    844 
    845             if (name.equals("Default")) {
    846                 List<TechniqueDef> techDefs = def.getDefaultTechniques();
    847                 if (techDefs == null || techDefs.isEmpty()) {
    848                     throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'");
    849                 }
    850 
    851                 TechniqueDef lastTech = null;
    852                 for (TechniqueDef techDef : techDefs) {
    853                     if (rendererCaps.containsAll(techDef.getRequiredCaps())) {
    854                         // use the first one that supports all the caps
    855                         tech = new Technique(this, techDef);
    856                         techniques.put(name, tech);
    857                         break;
    858                     }
    859                     lastTech = techDef;
    860                 }
    861                 if (tech == null) {
    862                     throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n"
    863                             + " is supported by the video hardware. The caps "
    864                             + lastTech.getRequiredCaps() + " are required.");
    865                 }
    866 
    867             } else {
    868                 // create "special" technique instance
    869                 TechniqueDef techDef = def.getTechniqueDef(name);
    870                 if (techDef == null) {
    871                     throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name);
    872                 }
    873 
    874                 if (!rendererCaps.containsAll(techDef.getRequiredCaps())) {
    875                     throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n"
    876                             + "requires caps " + techDef.getRequiredCaps() + " which are not "
    877                             + "supported by the video renderer");
    878                 }
    879 
    880                 tech = new Technique(this, techDef);
    881                 techniques.put(name, tech);
    882             }
    883         } else if (technique == tech) {
    884             // attempting to switch to an already
    885             // active technique.
    886             return;
    887         }
    888 
    889         technique = tech;
    890         tech.makeCurrent(def.getAssetManager());
    891 
    892         // shader was changed
    893         sortingId = -1;
    894     }
    895 
    896     private void autoSelectTechnique(RenderManager rm) {
    897         if (technique == null) {
    898             // NOTE: Not really needed anymore since we have technique
    899             // selection by caps. Rename all "FixedFunc" techniques to "Default"
    900             // and remove this hack.
    901             if (!rm.getRenderer().getCaps().contains(Caps.GLSL100)) {
    902                 selectTechnique("FixedFunc", rm);
    903             } else {
    904                 selectTechnique("Default", rm);
    905             }
    906         } else if (technique.isNeedReload()) {
    907             technique.makeCurrent(def.getAssetManager());
    908         }
    909     }
    910 
    911     /**
    912      * Preloads this material for the given render manager.
    913      * <p>
    914      * Preloading the material can ensure that when the material is first
    915      * used for rendering, there won't be any delay since the material has
    916      * been already been setup for rendering.
    917      *
    918      * @param rm The render manager to preload for
    919      */
    920     public void preload(RenderManager rm) {
    921         autoSelectTechnique(rm);
    922 
    923         Renderer r = rm.getRenderer();
    924         TechniqueDef techDef = technique.getDef();
    925 
    926         Collection<MatParam> params = paramValues.values();
    927         for (MatParam param : params) {
    928             if (param instanceof MatParamTexture) {
    929                 MatParamTexture texParam = (MatParamTexture) param;
    930                 r.setTexture(0, texParam.getTextureValue());
    931             } else {
    932                 if (!techDef.isUsingShaders()) {
    933                     continue;
    934                 }
    935 
    936                 technique.updateUniformParam(param.getName(),
    937                         param.getVarType(),
    938                         param.getValue(), true);
    939             }
    940         }
    941 
    942         Shader shader = technique.getShader();
    943         if (techDef.isUsingShaders()) {
    944             r.setShader(shader);
    945         }
    946     }
    947 
    948     private void clearUniformsSetByCurrent(Shader shader) {
    949         ListMap<String, Uniform> uniforms = shader.getUniformMap();
    950         int size = uniforms.size();
    951         for (int i = 0; i < size; i++) {
    952             Uniform u = uniforms.getValue(i);
    953             u.clearSetByCurrentMaterial();
    954         }
    955     }
    956 
    957     private void resetUniformsNotSetByCurrent(Shader shader) {
    958         ListMap<String, Uniform> uniforms = shader.getUniformMap();
    959         int size = uniforms.size();
    960         for (int i = 0; i < size; i++) {
    961             Uniform u = uniforms.getValue(i);
    962             if (!u.isSetByCurrentMaterial()) {
    963                 u.clearValue();
    964             }
    965         }
    966     }
    967 
    968     /**
    969      * Called by {@link RenderManager} to render the geometry by
    970      * using this material.
    971      *
    972      * @param geom The geometry to render
    973      * @param rm The render manager requesting the rendering
    974      */
    975     public void render(Geometry geom, RenderManager rm) {
    976         autoSelectTechnique(rm);
    977 
    978         Renderer r = rm.getRenderer();
    979 
    980         TechniqueDef techDef = technique.getDef();
    981 
    982         if (techDef.getLightMode() == LightMode.MultiPass
    983                 && geom.getWorldLightList().size() == 0) {
    984             return;
    985         }
    986 
    987         if (rm.getForcedRenderState() != null) {
    988             r.applyRenderState(rm.getForcedRenderState());
    989         } else {
    990             if (techDef.getRenderState() != null) {
    991                 r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
    992             } else {
    993                 r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
    994             }
    995         }
    996 
    997 
    998         // update camera and world matrices
    999         // NOTE: setWorldTransform should have been called already
   1000         if (techDef.isUsingShaders()) {
   1001             // reset unchanged uniform flag
   1002             clearUniformsSetByCurrent(technique.getShader());
   1003             rm.updateUniformBindings(technique.getWorldBindUniforms());
   1004         }
   1005 
   1006         // setup textures and uniforms
   1007         for (int i = 0; i < paramValues.size(); i++) {
   1008             MatParam param = paramValues.getValue(i);
   1009             param.apply(r, technique);
   1010         }
   1011 
   1012         Shader shader = technique.getShader();
   1013 
   1014         // send lighting information, if needed
   1015         switch (techDef.getLightMode()) {
   1016             case Disable:
   1017                 r.setLighting(null);
   1018                 break;
   1019             case SinglePass:
   1020                 updateLightListUniforms(shader, geom, 4);
   1021                 break;
   1022             case FixedPipeline:
   1023                 r.setLighting(geom.getWorldLightList());
   1024                 break;
   1025             case MultiPass:
   1026                 // NOTE: Special case!
   1027                 resetUniformsNotSetByCurrent(shader);
   1028                 renderMultipassLighting(shader, geom, rm);
   1029                 // very important, notice the return statement!
   1030                 return;
   1031         }
   1032 
   1033         // upload and bind shader
   1034         if (techDef.isUsingShaders()) {
   1035             // any unset uniforms will be set to 0
   1036             resetUniformsNotSetByCurrent(shader);
   1037             r.setShader(shader);
   1038         }
   1039 
   1040         r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1);
   1041     }
   1042 
   1043     public void write(JmeExporter ex) throws IOException {
   1044         OutputCapsule oc = ex.getCapsule(this);
   1045         oc.write(def.getAssetName(), "material_def", null);
   1046         oc.write(additionalState, "render_state", null);
   1047         oc.write(transparent, "is_transparent", false);
   1048         oc.writeStringSavableMap(paramValues, "parameters", null);
   1049     }
   1050 
   1051     public void read(JmeImporter im) throws IOException {
   1052         InputCapsule ic = im.getCapsule(this);
   1053 
   1054         additionalState = (RenderState) ic.readSavable("render_state", null);
   1055         transparent = ic.readBoolean("is_transparent", false);
   1056 
   1057         // Load the material def
   1058         String defName = ic.readString("material_def", null);
   1059         HashMap<String, MatParam> params = (HashMap<String, MatParam>) ic.readStringSavableMap("parameters", null);
   1060 
   1061         boolean enableVcolor = false;
   1062         boolean separateTexCoord = false;
   1063         boolean applyDefaultValues = false;
   1064         boolean guessRenderStateApply = false;
   1065 
   1066         int ver = ic.getSavableVersion(Material.class);
   1067         if (ver < 1){
   1068             applyDefaultValues = true;
   1069         }
   1070         if (ver < 2){
   1071             guessRenderStateApply = true;
   1072         }
   1073         if (im.getFormatVersion() == 0) {
   1074             // Enable compatibility with old models
   1075             if (defName.equalsIgnoreCase("Common/MatDefs/Misc/VertexColor.j3md")) {
   1076                 // Using VertexColor, switch to Unshaded and set VertexColor=true
   1077                 enableVcolor = true;
   1078                 defName = "Common/MatDefs/Misc/Unshaded.j3md";
   1079             } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/SimpleTextured.j3md")
   1080                     || defName.equalsIgnoreCase("Common/MatDefs/Misc/SolidColor.j3md")) {
   1081                 // Using SimpleTextured/SolidColor, just switch to Unshaded
   1082                 defName = "Common/MatDefs/Misc/Unshaded.j3md";
   1083             } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/WireColor.j3md")) {
   1084                 // Using WireColor, set wireframe renderstate = true and use Unshaded
   1085                 getAdditionalRenderState().setWireframe(true);
   1086                 defName = "Common/MatDefs/Misc/Unshaded.j3md";
   1087             } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/Unshaded.j3md")) {
   1088                 // Uses unshaded, ensure that the proper param is set
   1089                 MatParam value = params.get("SeperateTexCoord");
   1090                 if (value != null && ((Boolean) value.getValue()) == true) {
   1091                     params.remove("SeperateTexCoord");
   1092                     separateTexCoord = true;
   1093                 }
   1094             }
   1095             assert applyDefaultValues && guessRenderStateApply;
   1096         }
   1097 
   1098         def = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName));
   1099         paramValues = new ListMap<String, MatParam>();
   1100 
   1101         // load the textures and update nextTexUnit
   1102         for (Map.Entry<String, MatParam> entry : params.entrySet()) {
   1103             MatParam param = entry.getValue();
   1104             if (param instanceof MatParamTexture) {
   1105                 MatParamTexture texVal = (MatParamTexture) param;
   1106 
   1107                 if (nextTexUnit < texVal.getUnit() + 1) {
   1108                     nextTexUnit = texVal.getUnit() + 1;
   1109                 }
   1110 
   1111                 // the texture failed to load for this param
   1112                 // do not add to param values
   1113                 if (texVal.getTextureValue() == null || texVal.getTextureValue().getImage() == null) {
   1114                     continue;
   1115                 }
   1116             }
   1117             param.setName(checkSetParam(param.getVarType(), param.getName()));
   1118             paramValues.put(param.getName(), param);
   1119         }
   1120 
   1121         if (applyDefaultValues){
   1122             // compatability with old versions where default vars were
   1123             // not available
   1124             for (MatParam param : def.getMaterialParams()){
   1125                 if (param.getValue() != null && paramValues.get(param.getName()) == null){
   1126                     setParam(param.getName(), param.getVarType(), param.getValue());
   1127                 }
   1128             }
   1129         }
   1130         if (guessRenderStateApply && additionalState != null){
   1131             // Try to guess values of "apply" render state based on defaults
   1132             // if value != default then set apply to true
   1133             additionalState.applyPolyOffset = additionalState.offsetEnabled;
   1134             additionalState.applyAlphaFallOff = additionalState.alphaTest;
   1135             additionalState.applyAlphaTest = additionalState.alphaTest;
   1136             additionalState.applyBlendMode = additionalState.blendMode != BlendMode.Off;
   1137             additionalState.applyColorWrite = !additionalState.colorWrite;
   1138             additionalState.applyCullMode = additionalState.cullMode != FaceCullMode.Back;
   1139             additionalState.applyDepthTest = !additionalState.depthTest;
   1140             additionalState.applyDepthWrite = !additionalState.depthWrite;
   1141             additionalState.applyPointSprite = additionalState.pointSprite;
   1142             additionalState.applyStencilTest = additionalState.stencilTest;
   1143             additionalState.applyWireFrame = additionalState.wireframe;
   1144         }
   1145         if (enableVcolor) {
   1146             setBoolean("VertexColor", true);
   1147         }
   1148         if (separateTexCoord) {
   1149             setBoolean("SeparateTexCoord", true);
   1150         }
   1151     }
   1152 }
   1153