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 
Material(MaterialDef def)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 
Material(AssetManager contentMan, String defName)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      */
Material()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      */
getAssetName()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      */
getName()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      */
setName(String name)150     public void setName(String name) {
151 		this.name = name;
152 	}
153 
setKey(AssetKey key)154     public void setKey(AssetKey key) {
155         this.key = key;
156     }
157 
getKey()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      */
getSortId()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      */
compareTo(Material m)201     public int compareTo(Material m) {
202         return m.getSortId() - getSortId();
203     }
204 
205     @Override
equals(Object obj)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
clone()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      */
getActiveTechnique()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      */
isTransparent()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      */
setTransparent(boolean transparent)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      */
isReceivesShadows()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      */
setReceivesShadows(boolean receivesShadows)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      */
getAdditionalRenderState()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      */
getMaterialDef()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      */
getParam(String name)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      */
getTextureParam(String name)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      */
getParams()367     public Collection<MatParam> getParams() {
368         return paramValues.values();
369     }
370 
checkSetParam(VarType type, String name)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      */
setParam(String name, VarType type, Object value)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      */
clearParam(String name)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 
clearTextureParam(String name)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      */
setTextureParam(String name, VarType type, Texture value)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      */
setTexture(String name, Texture value)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      */
setMatrix4(String name, Matrix4f value)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      */
setBoolean(String name, boolean value)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      */
setFloat(String name, float value)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      */
setInt(String name, int value)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      */
setColor(String name, ColorRGBA value)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      */
setVector2(String name, Vector2f value)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      */
setVector3(String name, Vector3f value)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      */
setVector4(String name, Vector4f value)613     public void setVector4(String name, Vector4f value) {
614         setParam(name, VarType.Vector4, value);
615     }
616 
getAmbientColor(LightList lightList)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      */
updateLightListUniforms(Shader shader, Geometry g, int numLights)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 
renderMultipassLighting(Shader shader, Geometry g, RenderManager rm)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      */
selectTechnique(String name, RenderManager renderManager)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 
autoSelectTechnique(RenderManager rm)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      */
preload(RenderManager rm)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 
clearUniformsSetByCurrent(Shader shader)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 
resetUniformsNotSetByCurrent(Shader shader)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      */
render(Geometry geom, RenderManager rm)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 
write(JmeExporter ex)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 
read(JmeImporter im)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