diff options
Diffstat (limited to 'engine/src/core/com/jme3/material/Material.java')
-rw-r--r-- | engine/src/core/com/jme3/material/Material.java | 1152 |
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); + } + } +} |