OSDN Git Service

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