aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/material/Material.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/material/Material.java')
-rw-r--r--engine/src/core/com/jme3/material/Material.java1152
1 files changed, 1152 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/material/Material.java b/engine/src/core/com/jme3/material/Material.java
new file mode 100644
index 0000000..8d43853
--- /dev/null
+++ b/engine/src/core/com/jme3/material/Material.java
@@ -0,0 +1,1152 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * <p/>
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * <p/>
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material;
+
+import com.jme3.asset.Asset;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.export.*;
+import com.jme3.light.*;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.material.TechniqueDef.LightMode;
+import com.jme3.material.TechniqueDef.ShadowMode;
+import com.jme3.math.*;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import com.jme3.util.ListMap;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Material</code> describes the rendering style for a given
+ * {@link Geometry}.
+ * <p>A material is essentially a list of {@link MatParam parameters},
+ * those parameters map to uniforms which are defined in a shader.
+ * Setting the parameters can modify the behavior of a
+ * shader.
+ * <p/>
+ * @author Kirill Vainer
+ */
+public class Material implements Asset, Cloneable, Savable, Comparable<Material> {
+
+ // Version #2: Fixed issue with RenderState.apply*** flags not getting exported
+ public static final int SAVABLE_VERSION = 2;
+
+ private static final Logger logger = Logger.getLogger(Material.class.getName());
+ private static final RenderState additiveLight = new RenderState();
+ private static final RenderState depthOnly = new RenderState();
+ private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1);
+
+ static {
+ depthOnly.setDepthTest(true);
+ depthOnly.setDepthWrite(true);
+ depthOnly.setFaceCullMode(RenderState.FaceCullMode.Back);
+ depthOnly.setColorWrite(false);
+
+ additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive);
+ additiveLight.setDepthWrite(false);
+ }
+ private AssetKey key;
+ private String name;
+ private MaterialDef def;
+ private ListMap<String, MatParam> paramValues = new ListMap<String, MatParam>();
+ private Technique technique;
+ private HashMap<String, Technique> techniques = new HashMap<String, Technique>();
+ private int nextTexUnit = 0;
+ private RenderState additionalState = null;
+ private RenderState mergedRenderState = new RenderState();
+ private boolean transparent = false;
+ private boolean receivesShadows = false;
+ private int sortingId = -1;
+ private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+
+ public Material(MaterialDef def) {
+ if (def == null) {
+ throw new NullPointerException("Material definition cannot be null");
+ }
+ this.def = def;
+
+ // Load default values from definition (if any)
+ for (MatParam param : def.getMaterialParams()){
+ if (param.getValue() != null){
+ setParam(param.getName(), param.getVarType(), param.getValue());
+ }
+ }
+ }
+
+ public Material(AssetManager contentMan, String defName) {
+ this((MaterialDef) contentMan.loadAsset(new AssetKey(defName)));
+ }
+
+ /**
+ * Do not use this constructor. Serialization purposes only.
+ */
+ public Material() {
+ }
+
+ /**
+ * Returns the asset key name of the asset from which this material was loaded.
+ *
+ * <p>This value will be <code>null</code> unless this material was loaded
+ * from a .j3m file.
+ *
+ * @return Asset key name of the j3m file
+ */
+ public String getAssetName() {
+ return key != null ? key.getName() : null;
+ }
+
+ /**
+ * @return the name of the material (not the same as the asset name), the returned value can be null
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * This method sets the name of the material.
+ * The name is not the same as the asset name.
+ * It can be null and there is no guarantee of its uniqness.
+ * @param name the name of the material
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setKey(AssetKey key) {
+ this.key = key;
+ }
+
+ public AssetKey getKey() {
+ return key;
+ }
+
+ /**
+ * Returns the sorting ID or sorting index for this material.
+ *
+ * <p>The sorting ID is used internally by the system to sort rendering
+ * of geometries. It sorted to reduce shader switches, if the shaders
+ * are equal, then it is sorted by textures.
+ *
+ * @return The sorting ID used for sorting geometries for rendering.
+ */
+ public int getSortId() {
+ Technique t = getActiveTechnique();
+ if (sortingId == -1 && t != null && t.getShader() != null) {
+ int texId = -1;
+ for (int i = 0; i < paramValues.size(); i++) {
+ MatParam param = paramValues.getValue(i);
+ if (param instanceof MatParamTexture) {
+ MatParamTexture tex = (MatParamTexture) param;
+ if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) {
+ if (texId == -1) {
+ texId = 0;
+ }
+ texId += tex.getTextureValue().getImage().getId() % 0xff;
+ }
+ }
+ }
+ sortingId = texId + t.getShader().getId() * 1000;
+ }
+ return sortingId;
+ }
+
+ /**
+ * Uses the sorting ID for each material to compare them.
+ *
+ * @param m The other material to compare to.
+ *
+ * @return zero if the materials are equal, returns a negative value
+ * if <code>this</code> has a lower sorting ID than <code>m</code>,
+ * otherwise returns a positive value.
+ */
+ public int compareTo(Material m) {
+ return m.getSortId() - getSortId();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(obj instanceof Material){
+ return ((Material)obj).compareTo(this) == 0;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * Clones this material. The result is returned.
+ */
+ @Override
+ public Material clone() {
+ try {
+ Material mat = (Material) super.clone();
+
+ if (additionalState != null) {
+ mat.additionalState = additionalState.clone();
+ }
+ mat.technique = null;
+ mat.techniques = new HashMap<String, Technique>();
+
+ mat.paramValues = new ListMap<String, MatParam>();
+ for (int i = 0; i < paramValues.size(); i++) {
+ Map.Entry<String, MatParam> entry = paramValues.getEntry(i);
+ mat.paramValues.put(entry.getKey(), entry.getValue().clone());
+ }
+
+ return mat;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Returns the currently active technique.
+ * <p>
+ * The technique is selected automatically by the {@link RenderManager}
+ * based on system capabilities. Users may select their own
+ * technique by using
+ * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }.
+ *
+ * @return the currently active technique.
+ *
+ * @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager)
+ */
+ public Technique getActiveTechnique() {
+ return technique;
+ }
+
+ /**
+ * Check if the transparent value marker is set on this material.
+ * @return True if the transparent value marker is set on this material.
+ * @see #setTransparent(boolean)
+ */
+ public boolean isTransparent() {
+ return transparent;
+ }
+
+ /**
+ * Set the transparent value marker.
+ *
+ * <p>This value is merely a marker, by itself it does nothing.
+ * Generally model loaders will use this marker to indicate further
+ * up that the material is transparent and therefore any geometries
+ * using it should be put into the {@link Bucket#Transparent transparent
+ * bucket}.
+ *
+ * @param transparent the transparent value marker.
+ */
+ public void setTransparent(boolean transparent) {
+ this.transparent = transparent;
+ }
+
+ /**
+ * Check if the material should receive shadows or not.
+ *
+ * @return True if the material should receive shadows.
+ *
+ * @see Material#setReceivesShadows(boolean)
+ */
+ public boolean isReceivesShadows() {
+ return receivesShadows;
+ }
+
+ /**
+ * Set if the material should receive shadows or not.
+ *
+ * <p>This value is merely a marker, by itself it does nothing.
+ * Generally model loaders will use this marker to indicate
+ * the material should receive shadows and therefore any
+ * geometries using it should have the {@link ShadowMode#Receive} set
+ * on them.
+ *
+ * @param receivesShadows if the material should receive shadows or not.
+ */
+ public void setReceivesShadows(boolean receivesShadows) {
+ this.receivesShadows = receivesShadows;
+ }
+
+ /**
+ * Acquire the additional {@link RenderState render state} to apply
+ * for this material.
+ *
+ * <p>The first call to this method will create an additional render
+ * state which can be modified by the user to apply any render
+ * states in addition to the ones used by the renderer. Only render
+ * states which are modified in the additional render state will be applied.
+ *
+ * @return The additional render state.
+ */
+ public RenderState getAdditionalRenderState() {
+ if (additionalState == null) {
+ additionalState = RenderState.ADDITIONAL.clone();
+ }
+ return additionalState;
+ }
+
+ /**
+ * Get the material definition (j3md file info) that <code>this</code>
+ * material is implementing.
+ *
+ * @return the material definition this material implements.
+ */
+ public MaterialDef getMaterialDef() {
+ return def;
+ }
+
+ /**
+ * Returns the parameter set on this material with the given name,
+ * returns <code>null</code> if the parameter is not set.
+ *
+ * @param name The parameter name to look up.
+ * @return The MatParam if set, or null if not set.
+ */
+ public MatParam getParam(String name) {
+ MatParam param = paramValues.get(name);
+ return param;
+ }
+
+ /**
+ * Returns the texture parameter set on this material with the given name,
+ * returns <code>null</code> if the parameter is not set.
+ *
+ * @param name The parameter name to look up.
+ * @return The MatParamTexture if set, or null if not set.
+ */
+ public MatParamTexture getTextureParam(String name) {
+ MatParam param = paramValues.get(name);
+ if (param instanceof MatParamTexture) {
+ return (MatParamTexture) param;
+ }
+ return null;
+ }
+
+ /**
+ * Returns a collection of all parameters set on this material.
+ *
+ * @return a collection of all parameters set on this material.
+ *
+ * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
+ */
+ public Collection<MatParam> getParams() {
+ return paramValues.values();
+ }
+
+ private String checkSetParam(VarType type, String name) {
+ MatParam paramDef = def.getMaterialParam(name);
+ String newName = name;
+
+ if (paramDef == null && name.startsWith("m_")) {
+ newName = name.substring(2);
+ paramDef = def.getMaterialParam(newName);
+ if (paramDef == null) {
+ throw new IllegalArgumentException("Material parameter is not defined: " + name);
+ } else {
+ logger.log(Level.WARNING, "Material parameter {0} uses a deprecated naming convention use {1} instead ", new Object[]{name, newName});
+ }
+ } else if (paramDef == null) {
+ throw new IllegalArgumentException("Material parameter is not defined: " + name);
+ }
+
+ if (type != null && paramDef.getVarType() != type) {
+ logger.log(Level.WARNING, "Material parameter being set: {0} with "
+ + "type {1} doesn''t match definition types {2}", new Object[]{name, type.name(), paramDef.getVarType()} );
+ }
+
+ return newName;
+ }
+
+ /**
+ * Pass a parameter to the material shader.
+ *
+ * @param name the name of the parameter defined in the material definition (j3md)
+ * @param type the type of the parameter {@link VarType}
+ * @param value the value of the parameter
+ */
+ public void setParam(String name, VarType type, Object value) {
+ name = checkSetParam(type, name);
+
+ MatParam val = getParam(name);
+ if (technique != null) {
+ technique.notifySetParam(name, type, value);
+ }
+ if (val == null) {
+ MatParam paramDef = def.getMaterialParam(name);
+ paramValues.put(name, new MatParam(type, name, value, paramDef.getFixedFuncBinding()));
+ } else {
+ val.setValue(value);
+ }
+ }
+
+ /**
+ * Clear a parameter from this material. The parameter must exist
+ * @param name the name of the parameter to clear
+ */
+ public void clearParam(String name) {
+ //On removal, we don't check if the param exists in the paramDef, and just go on with the process.
+ // name = checkSetParam(null, name);
+
+ MatParam matParam = getParam(name);
+ if (matParam != null) {
+ paramValues.remove(name);
+ if (technique != null) {
+ technique.notifyClearParam(name);
+ }
+ if (matParam instanceof MatParamTexture) {
+ int texUnit = ((MatParamTexture) matParam).getUnit();
+ nextTexUnit--;
+ for (MatParam param : paramValues.values()) {
+ if (param instanceof MatParamTexture) {
+ MatParamTexture texParam = (MatParamTexture) param;
+ if (texParam.getUnit() > texUnit) {
+ texParam.setUnit(texParam.getUnit() - 1);
+ }
+ }
+ }
+ }
+ }
+// else {
+// throw new IllegalArgumentException("The given parameter is not set.");
+// }
+ }
+
+ private void clearTextureParam(String name) {
+ name = checkSetParam(null, name);
+
+ MatParamTexture val = getTextureParam(name);
+ if (val == null) {
+ throw new IllegalArgumentException("The given texture for parameter \"" + name + "\" is null.");
+ }
+
+ int texUnit = val.getUnit();
+ paramValues.remove(name);
+ nextTexUnit--;
+ for (MatParam param : paramValues.values()) {
+ if (param instanceof MatParamTexture) {
+ MatParamTexture texParam = (MatParamTexture) param;
+ if (texParam.getUnit() > texUnit) {
+ texParam.setUnit(texParam.getUnit() - 1);
+ }
+ }
+ }
+
+ sortingId = -1;
+ }
+
+ /**
+ * Set a texture parameter.
+ *
+ * @param name The name of the parameter
+ * @param type The variable type {@link VarType}
+ * @param value The texture value of the parameter.
+ *
+ * @throws IllegalArgumentException is value is null
+ */
+ public void setTextureParam(String name, VarType type, Texture value) {
+ if (value == null) {
+ throw new IllegalArgumentException();
+ }
+
+ name = checkSetParam(type, name);
+ MatParamTexture val = getTextureParam(name);
+ if (val == null) {
+ paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++));
+ } else {
+ val.setTextureValue(value);
+ }
+
+ if (technique != null) {
+ technique.notifySetParam(name, type, nextTexUnit - 1);
+ }
+
+ // need to recompute sort ID
+ sortingId = -1;
+ }
+
+ /**
+ * Pass a texture to the material shader.
+ *
+ * @param name the name of the texture defined in the material definition
+ * (j3md) (for example Texture for Lighting.j3md)
+ * @param value the Texture object previously loaded by the asset manager
+ */
+ public void setTexture(String name, Texture value) {
+ if (value == null) {
+ // clear it
+ clearTextureParam(name);
+ return;
+ }
+
+ VarType paramType = null;
+ switch (value.getType()) {
+ case TwoDimensional:
+ paramType = VarType.Texture2D;
+ break;
+ case TwoDimensionalArray:
+ paramType = VarType.TextureArray;
+ break;
+ case ThreeDimensional:
+ paramType = VarType.Texture3D;
+ break;
+ case CubeMap:
+ paramType = VarType.TextureCubeMap;
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown texture type: " + value.getType());
+ }
+
+ setTextureParam(name, paramType, value);
+ }
+
+ /**
+ * Pass a Matrix4f to the material shader.
+ *
+ * @param name the name of the matrix defined in the material definition (j3md)
+ * @param value the Matrix4f object
+ */
+ public void setMatrix4(String name, Matrix4f value) {
+ setParam(name, VarType.Matrix4, value);
+ }
+
+ /**
+ * Pass a boolean to the material shader.
+ *
+ * @param name the name of the boolean defined in the material definition (j3md)
+ * @param value the boolean value
+ */
+ public void setBoolean(String name, boolean value) {
+ setParam(name, VarType.Boolean, value);
+ }
+
+ /**
+ * Pass a float to the material shader.
+ *
+ * @param name the name of the float defined in the material definition (j3md)
+ * @param value the float value
+ */
+ public void setFloat(String name, float value) {
+ setParam(name, VarType.Float, value);
+ }
+
+ /**
+ * Pass an int to the material shader.
+ *
+ * @param name the name of the int defined in the material definition (j3md)
+ * @param value the int value
+ */
+ public void setInt(String name, int value) {
+ setParam(name, VarType.Int, value);
+ }
+
+ /**
+ * Pass a Color to the material shader.
+ *
+ * @param name the name of the color defined in the material definition (j3md)
+ * @param value the ColorRGBA value
+ */
+ public void setColor(String name, ColorRGBA value) {
+ setParam(name, VarType.Vector4, value);
+ }
+
+ /**
+ * Pass a Vector2f to the material shader.
+ *
+ * @param name the name of the Vector2f defined in the material definition (j3md)
+ * @param value the Vector2f value
+ */
+ public void setVector2(String name, Vector2f value) {
+ setParam(name, VarType.Vector2, value);
+ }
+
+ /**
+ * Pass a Vector3f to the material shader.
+ *
+ * @param name the name of the Vector3f defined in the material definition (j3md)
+ * @param value the Vector3f value
+ */
+ public void setVector3(String name, Vector3f value) {
+ setParam(name, VarType.Vector3, value);
+ }
+
+ /**
+ * Pass a Vector4f to the material shader.
+ *
+ * @param name the name of the Vector4f defined in the material definition (j3md)
+ * @param value the Vector4f value
+ */
+ public void setVector4(String name, Vector4f value) {
+ setParam(name, VarType.Vector4, value);
+ }
+
+ private ColorRGBA getAmbientColor(LightList lightList) {
+ ambientLightColor.set(0, 0, 0, 1);
+ for (int j = 0; j < lightList.size(); j++) {
+ Light l = lightList.get(j);
+ if (l instanceof AmbientLight) {
+ ambientLightColor.addLocal(l.getColor());
+ }
+ }
+ ambientLightColor.a = 1.0f;
+ return ambientLightColor;
+ }
+
+ /**
+ * Uploads the lights in the light list as two uniform arrays.<br/><br/>
+ * * <p>
+ * <code>uniform vec4 g_LightColor[numLights];</code><br/>
+ * // g_LightColor.rgb is the diffuse/specular color of the light.<br/>
+ * // g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/>
+ * // 2 = Spot. <br/>
+ * <br/>
+ * <code>uniform vec4 g_LightPosition[numLights];</code><br/>
+ * // g_LightPosition.xyz is the position of the light (for point lights)<br/>
+ * // or the direction of the light (for directional lights).<br/>
+ * // g_LightPosition.w is the inverse radius (1/r) of the light (for attenuation) <br/>
+ * </p>
+ */
+ protected void updateLightListUniforms(Shader shader, Geometry g, int numLights) {
+ if (numLights == 0) { // this shader does not do lighting, ignore.
+ return;
+ }
+
+ LightList lightList = g.getWorldLightList();
+ Uniform lightColor = shader.getUniform("g_LightColor");
+ Uniform lightPos = shader.getUniform("g_LightPosition");
+ Uniform lightDir = shader.getUniform("g_LightDirection");
+ lightColor.setVector4Length(numLights);
+ lightPos.setVector4Length(numLights);
+ lightDir.setVector4Length(numLights);
+
+ Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+ ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
+
+ int lightIndex = 0;
+
+ for (int i = 0; i < numLights; i++) {
+ if (lightList.size() <= i) {
+ lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+ lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+ } else {
+ Light l = lightList.get(i);
+ ColorRGBA color = l.getColor();
+ lightColor.setVector4InArray(color.getRed(),
+ color.getGreen(),
+ color.getBlue(),
+ l.getType().getId(),
+ i);
+
+ switch (l.getType()) {
+ case Directional:
+ DirectionalLight dl = (DirectionalLight) l;
+ Vector3f dir = dl.getDirection();
+ lightPos.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightIndex);
+ break;
+ case Point:
+ PointLight pl = (PointLight) l;
+ Vector3f pos = pl.getPosition();
+ float invRadius = pl.getInvRadius();
+ lightPos.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightIndex);
+ break;
+ case Spot:
+ SpotLight sl = (SpotLight) l;
+ Vector3f pos2 = sl.getPosition();
+ Vector3f dir2 = sl.getDirection();
+ float invRange = sl.getInvSpotRange();
+ float spotAngleCos = sl.getPackedAngleCos();
+
+ lightPos.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightIndex);
+ lightDir.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightIndex);
+ break;
+ case Ambient:
+ // skip this light. Does not increase lightIndex
+ continue;
+ default:
+ throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+ }
+ }
+
+ lightIndex++;
+ }
+
+ while (lightIndex < numLights) {
+ lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+ lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+
+ lightIndex++;
+ }
+ }
+
+ protected void renderMultipassLighting(Shader shader, Geometry g, RenderManager rm) {
+
+ Renderer r = rm.getRenderer();
+ LightList lightList = g.getWorldLightList();
+ Uniform lightDir = shader.getUniform("g_LightDirection");
+ Uniform lightColor = shader.getUniform("g_LightColor");
+ Uniform lightPos = shader.getUniform("g_LightPosition");
+ Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+ boolean isFirstLight = true;
+ boolean isSecondLight = false;
+
+ for (int i = 0; i < lightList.size(); i++) {
+ Light l = lightList.get(i);
+ if (l instanceof AmbientLight) {
+ continue;
+ }
+
+ if (isFirstLight) {
+ // set ambient color for first light only
+ ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
+ isFirstLight = false;
+ isSecondLight = true;
+ } else if (isSecondLight) {
+ ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+ // apply additive blending for 2nd and future lights
+ r.applyRenderState(additiveLight);
+ isSecondLight = false;
+ }
+
+ TempVars vars = TempVars.get();
+ Quaternion tmpLightDirection = vars.quat1;
+ Quaternion tmpLightPosition = vars.quat2;
+ ColorRGBA tmpLightColor = vars.color;
+ Vector4f tmpVec = vars.vect4f;
+
+ ColorRGBA color = l.getColor();
+ tmpLightColor.set(color);
+ tmpLightColor.a = l.getType().getId();
+ lightColor.setValue(VarType.Vector4, tmpLightColor);
+
+ switch (l.getType()) {
+ case Directional:
+ DirectionalLight dl = (DirectionalLight) l;
+ Vector3f dir = dl.getDirection();
+
+ tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
+ lightPos.setValue(VarType.Vector4, tmpLightPosition);
+ tmpLightDirection.set(0, 0, 0, 0);
+ lightDir.setValue(VarType.Vector4, tmpLightDirection);
+ break;
+ case Point:
+ PointLight pl = (PointLight) l;
+ Vector3f pos = pl.getPosition();
+ float invRadius = pl.getInvRadius();
+
+ tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
+ lightPos.setValue(VarType.Vector4, tmpLightPosition);
+ tmpLightDirection.set(0, 0, 0, 0);
+ lightDir.setValue(VarType.Vector4, tmpLightDirection);
+ break;
+ case Spot:
+ SpotLight sl = (SpotLight) l;
+ Vector3f pos2 = sl.getPosition();
+ Vector3f dir2 = sl.getDirection();
+ float invRange = sl.getInvSpotRange();
+ float spotAngleCos = sl.getPackedAngleCos();
+
+ tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
+ lightPos.setValue(VarType.Vector4, tmpLightPosition);
+
+ //We transform the spot directoin in view space here to save 5 varying later in the lighting shader
+ //one vec4 less and a vec4 that becomes a vec3
+ //the downside is that spotAngleCos decoding happen now in the frag shader.
+ tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),0);
+ rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+ tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
+
+ lightDir.setValue(VarType.Vector4, tmpLightDirection);
+
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+ }
+ vars.release();
+ r.setShader(shader);
+ r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+ }
+
+ if (isFirstLight && lightList.size() > 0) {
+ // There are only ambient lights in the scene. Render
+ // a dummy "normal light" so we can see the ambient
+ ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
+ lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
+ lightPos.setValue(VarType.Vector4, nullDirLight);
+ r.setShader(shader);
+ r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+ }
+ }
+
+ /**
+ * Select the technique to use for rendering this material.
+ * <p>
+ * If <code>name</code> is "Default", then one of the
+ * {@link MaterialDef#getDefaultTechniques() default techniques}
+ * on the material will be selected. Otherwise, the named technique
+ * will be found in the material definition.
+ * <p>
+ * Any candidate technique for selection (either default or named)
+ * must be verified to be compatible with the system, for that, the
+ * <code>renderManager</code> is queried for capabilities.
+ *
+ * @param name The name of the technique to select, pass "Default" to
+ * select one of the default techniques.
+ * @param renderManager The {@link RenderManager render manager}
+ * to query for capabilities.
+ *
+ * @throws IllegalArgumentException If "Default" is passed and no default
+ * techniques are available on the material definition, or if a name
+ * is passed but there's no technique by that name.
+ * @throws UnsupportedOperationException If no candidate technique supports
+ * the system capabilities.
+ */
+ public void selectTechnique(String name, RenderManager renderManager) {
+ // check if already created
+ Technique tech = techniques.get(name);
+ if (tech == null) {
+ // When choosing technique, we choose one that
+ // supports all the caps.
+ EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
+
+ if (name.equals("Default")) {
+ List<TechniqueDef> techDefs = def.getDefaultTechniques();
+ if (techDefs == null || techDefs.isEmpty()) {
+ throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'");
+ }
+
+ TechniqueDef lastTech = null;
+ for (TechniqueDef techDef : techDefs) {
+ if (rendererCaps.containsAll(techDef.getRequiredCaps())) {
+ // use the first one that supports all the caps
+ tech = new Technique(this, techDef);
+ techniques.put(name, tech);
+ break;
+ }
+ lastTech = techDef;
+ }
+ if (tech == null) {
+ throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n"
+ + " is supported by the video hardware. The caps "
+ + lastTech.getRequiredCaps() + " are required.");
+ }
+
+ } else {
+ // create "special" technique instance
+ TechniqueDef techDef = def.getTechniqueDef(name);
+ if (techDef == null) {
+ throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name);
+ }
+
+ if (!rendererCaps.containsAll(techDef.getRequiredCaps())) {
+ throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n"
+ + "requires caps " + techDef.getRequiredCaps() + " which are not "
+ + "supported by the video renderer");
+ }
+
+ tech = new Technique(this, techDef);
+ techniques.put(name, tech);
+ }
+ } else if (technique == tech) {
+ // attempting to switch to an already
+ // active technique.
+ return;
+ }
+
+ technique = tech;
+ tech.makeCurrent(def.getAssetManager());
+
+ // shader was changed
+ sortingId = -1;
+ }
+
+ private void autoSelectTechnique(RenderManager rm) {
+ if (technique == null) {
+ // NOTE: Not really needed anymore since we have technique
+ // selection by caps. Rename all "FixedFunc" techniques to "Default"
+ // and remove this hack.
+ if (!rm.getRenderer().getCaps().contains(Caps.GLSL100)) {
+ selectTechnique("FixedFunc", rm);
+ } else {
+ selectTechnique("Default", rm);
+ }
+ } else if (technique.isNeedReload()) {
+ technique.makeCurrent(def.getAssetManager());
+ }
+ }
+
+ /**
+ * Preloads this material for the given render manager.
+ * <p>
+ * Preloading the material can ensure that when the material is first
+ * used for rendering, there won't be any delay since the material has
+ * been already been setup for rendering.
+ *
+ * @param rm The render manager to preload for
+ */
+ public void preload(RenderManager rm) {
+ autoSelectTechnique(rm);
+
+ Renderer r = rm.getRenderer();
+ TechniqueDef techDef = technique.getDef();
+
+ Collection<MatParam> params = paramValues.values();
+ for (MatParam param : params) {
+ if (param instanceof MatParamTexture) {
+ MatParamTexture texParam = (MatParamTexture) param;
+ r.setTexture(0, texParam.getTextureValue());
+ } else {
+ if (!techDef.isUsingShaders()) {
+ continue;
+ }
+
+ technique.updateUniformParam(param.getName(),
+ param.getVarType(),
+ param.getValue(), true);
+ }
+ }
+
+ Shader shader = technique.getShader();
+ if (techDef.isUsingShaders()) {
+ r.setShader(shader);
+ }
+ }
+
+ private void clearUniformsSetByCurrent(Shader shader) {
+ ListMap<String, Uniform> uniforms = shader.getUniformMap();
+ int size = uniforms.size();
+ for (int i = 0; i < size; i++) {
+ Uniform u = uniforms.getValue(i);
+ u.clearSetByCurrentMaterial();
+ }
+ }
+
+ private void resetUniformsNotSetByCurrent(Shader shader) {
+ ListMap<String, Uniform> uniforms = shader.getUniformMap();
+ int size = uniforms.size();
+ for (int i = 0; i < size; i++) {
+ Uniform u = uniforms.getValue(i);
+ if (!u.isSetByCurrentMaterial()) {
+ u.clearValue();
+ }
+ }
+ }
+
+ /**
+ * Called by {@link RenderManager} to render the geometry by
+ * using this material.
+ *
+ * @param geom The geometry to render
+ * @param rm The render manager requesting the rendering
+ */
+ public void render(Geometry geom, RenderManager rm) {
+ autoSelectTechnique(rm);
+
+ Renderer r = rm.getRenderer();
+
+ TechniqueDef techDef = technique.getDef();
+
+ if (techDef.getLightMode() == LightMode.MultiPass
+ && geom.getWorldLightList().size() == 0) {
+ return;
+ }
+
+ if (rm.getForcedRenderState() != null) {
+ r.applyRenderState(rm.getForcedRenderState());
+ } else {
+ if (techDef.getRenderState() != null) {
+ r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
+ } else {
+ r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
+ }
+ }
+
+
+ // update camera and world matrices
+ // NOTE: setWorldTransform should have been called already
+ if (techDef.isUsingShaders()) {
+ // reset unchanged uniform flag
+ clearUniformsSetByCurrent(technique.getShader());
+ rm.updateUniformBindings(technique.getWorldBindUniforms());
+ }
+
+ // setup textures and uniforms
+ for (int i = 0; i < paramValues.size(); i++) {
+ MatParam param = paramValues.getValue(i);
+ param.apply(r, technique);
+ }
+
+ Shader shader = technique.getShader();
+
+ // send lighting information, if needed
+ switch (techDef.getLightMode()) {
+ case Disable:
+ r.setLighting(null);
+ break;
+ case SinglePass:
+ updateLightListUniforms(shader, geom, 4);
+ break;
+ case FixedPipeline:
+ r.setLighting(geom.getWorldLightList());
+ break;
+ case MultiPass:
+ // NOTE: Special case!
+ resetUniformsNotSetByCurrent(shader);
+ renderMultipassLighting(shader, geom, rm);
+ // very important, notice the return statement!
+ return;
+ }
+
+ // upload and bind shader
+ if (techDef.isUsingShaders()) {
+ // any unset uniforms will be set to 0
+ resetUniformsNotSetByCurrent(shader);
+ r.setShader(shader);
+ }
+
+ r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1);
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(def.getAssetName(), "material_def", null);
+ oc.write(additionalState, "render_state", null);
+ oc.write(transparent, "is_transparent", false);
+ oc.writeStringSavableMap(paramValues, "parameters", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+
+ additionalState = (RenderState) ic.readSavable("render_state", null);
+ transparent = ic.readBoolean("is_transparent", false);
+
+ // Load the material def
+ String defName = ic.readString("material_def", null);
+ HashMap<String, MatParam> params = (HashMap<String, MatParam>) ic.readStringSavableMap("parameters", null);
+
+ boolean enableVcolor = false;
+ boolean separateTexCoord = false;
+ boolean applyDefaultValues = false;
+ boolean guessRenderStateApply = false;
+
+ int ver = ic.getSavableVersion(Material.class);
+ if (ver < 1){
+ applyDefaultValues = true;
+ }
+ if (ver < 2){
+ guessRenderStateApply = true;
+ }
+ if (im.getFormatVersion() == 0) {
+ // Enable compatibility with old models
+ if (defName.equalsIgnoreCase("Common/MatDefs/Misc/VertexColor.j3md")) {
+ // Using VertexColor, switch to Unshaded and set VertexColor=true
+ enableVcolor = true;
+ defName = "Common/MatDefs/Misc/Unshaded.j3md";
+ } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/SimpleTextured.j3md")
+ || defName.equalsIgnoreCase("Common/MatDefs/Misc/SolidColor.j3md")) {
+ // Using SimpleTextured/SolidColor, just switch to Unshaded
+ defName = "Common/MatDefs/Misc/Unshaded.j3md";
+ } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/WireColor.j3md")) {
+ // Using WireColor, set wireframe renderstate = true and use Unshaded
+ getAdditionalRenderState().setWireframe(true);
+ defName = "Common/MatDefs/Misc/Unshaded.j3md";
+ } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/Unshaded.j3md")) {
+ // Uses unshaded, ensure that the proper param is set
+ MatParam value = params.get("SeperateTexCoord");
+ if (value != null && ((Boolean) value.getValue()) == true) {
+ params.remove("SeperateTexCoord");
+ separateTexCoord = true;
+ }
+ }
+ assert applyDefaultValues && guessRenderStateApply;
+ }
+
+ def = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName));
+ paramValues = new ListMap<String, MatParam>();
+
+ // load the textures and update nextTexUnit
+ for (Map.Entry<String, MatParam> entry : params.entrySet()) {
+ MatParam param = entry.getValue();
+ if (param instanceof MatParamTexture) {
+ MatParamTexture texVal = (MatParamTexture) param;
+
+ if (nextTexUnit < texVal.getUnit() + 1) {
+ nextTexUnit = texVal.getUnit() + 1;
+ }
+
+ // the texture failed to load for this param
+ // do not add to param values
+ if (texVal.getTextureValue() == null || texVal.getTextureValue().getImage() == null) {
+ continue;
+ }
+ }
+ param.setName(checkSetParam(param.getVarType(), param.getName()));
+ paramValues.put(param.getName(), param);
+ }
+
+ if (applyDefaultValues){
+ // compatability with old versions where default vars were
+ // not available
+ for (MatParam param : def.getMaterialParams()){
+ if (param.getValue() != null && paramValues.get(param.getName()) == null){
+ setParam(param.getName(), param.getVarType(), param.getValue());
+ }
+ }
+ }
+ if (guessRenderStateApply && additionalState != null){
+ // Try to guess values of "apply" render state based on defaults
+ // if value != default then set apply to true
+ additionalState.applyPolyOffset = additionalState.offsetEnabled;
+ additionalState.applyAlphaFallOff = additionalState.alphaTest;
+ additionalState.applyAlphaTest = additionalState.alphaTest;
+ additionalState.applyBlendMode = additionalState.blendMode != BlendMode.Off;
+ additionalState.applyColorWrite = !additionalState.colorWrite;
+ additionalState.applyCullMode = additionalState.cullMode != FaceCullMode.Back;
+ additionalState.applyDepthTest = !additionalState.depthTest;
+ additionalState.applyDepthWrite = !additionalState.depthWrite;
+ additionalState.applyPointSprite = additionalState.pointSprite;
+ additionalState.applyStencilTest = additionalState.stencilTest;
+ additionalState.applyWireFrame = additionalState.wireframe;
+ }
+ if (enableVcolor) {
+ setBoolean("VertexColor", true);
+ }
+ if (separateTexCoord) {
+ setBoolean("SeparateTexCoord", true);
+ }
+ }
+}