diff options
Diffstat (limited to 'engine/src/core/com/jme3/material')
-rw-r--r-- | engine/src/core/com/jme3/material/FixedFuncBinding.java | 80 | ||||
-rw-r--r-- | engine/src/core/com/jme3/material/MatParam.java | 353 | ||||
-rw-r--r-- | engine/src/core/com/jme3/material/MatParamTexture.java | 67 | ||||
-rw-r--r-- | engine/src/core/com/jme3/material/Material.java | 1152 | ||||
-rw-r--r-- | engine/src/core/com/jme3/material/MaterialDef.java | 190 | ||||
-rw-r--r-- | engine/src/core/com/jme3/material/MaterialList.java | 44 | ||||
-rw-r--r-- | engine/src/core/com/jme3/material/RenderState.java | 1070 | ||||
-rw-r--r-- | engine/src/core/com/jme3/material/Technique.java | 261 | ||||
-rw-r--r-- | engine/src/core/com/jme3/material/TechniqueDef.java | 396 | ||||
-rw-r--r-- | engine/src/core/com/jme3/material/package.html | 58 |
10 files changed, 3671 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/material/FixedFuncBinding.java b/engine/src/core/com/jme3/material/FixedFuncBinding.java new file mode 100644 index 0000000..e316ad8 --- /dev/null +++ b/engine/src/core/com/jme3/material/FixedFuncBinding.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * 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. + * + * * 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. + * + * * 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. + * + * 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; + +/** + * Fixed function binding is used to specify a binding for a {@link MatParam} + * in case that shaders are not supported on the system. + * + * @author Kirill Vainer + */ +public enum FixedFuncBinding { + /** + * Specifies the material ambient color. + * Same as GL_AMBIENT for OpenGL. + */ + MaterialAmbient, + + /** + * Specifies the material diffuse color. + * Same as GL_DIFFUSE for OpenGL. + */ + MaterialDiffuse, + + /** + * Specifies the material specular color. + * Same as GL_SPECULAR for OpenGL + */ + MaterialSpecular, + + /** + * Specifies the color of the object. + * <p> + * Used only for non-lit materials. + */ + Color, + + /** + * Specifies the material shininess value. + * + * Same as GL_SHININESS for OpenGL. + */ + MaterialShininess, + + /** + * Use vertex color as an additional diffuse color, if lighting is enabled. + * If lighting is disabled, vertex color is modulated with + * {@link #Color material color}. + */ + UseVertexColor +} diff --git a/engine/src/core/com/jme3/material/MatParam.java b/engine/src/core/com/jme3/material/MatParam.java new file mode 100644 index 0000000..b0ef117 --- /dev/null +++ b/engine/src/core/com/jme3/material/MatParam.java @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * 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. + * + * * 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. + * + * * 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. + * + * 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.TextureKey; +import com.jme3.export.*; +import com.jme3.math.*; +import com.jme3.renderer.GL1Renderer; +import com.jme3.renderer.Renderer; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import java.io.IOException; + +/** + * Describes a material parameter. This is used for both defining a name and type + * as well as a material parameter value. + * + * @author Kirill Vainer + */ +public class MatParam implements Savable, Cloneable { + + protected VarType type; + protected String name; + protected String prefixedName; + protected Object value; + protected FixedFuncBinding ffBinding; + + /** + * Create a new material parameter. For internal use only. + */ + public MatParam(VarType type, String name, Object value, FixedFuncBinding ffBinding) { + this.type = type; + this.name = name; + this.prefixedName = "m_" + name; + this.value = value; + this.ffBinding = ffBinding; + } + + /** + * Serialization only. Do not use. + */ + public MatParam() { + } + + /** + * Returns the fixed function binding. + * + * @return the fixed function binding. + */ + public FixedFuncBinding getFixedFuncBinding() { + return ffBinding; + } + + /** + * Returns the material parameter type. + * + * @return the material parameter type. + */ + public VarType getVarType() { + return type; + } + + /** + * Returns the name of the material parameter. + * @return the name of the material parameter. + */ + public String getName() { + return name; + } + + /** + * Returns the name with "m_" prefixed to it. + * + * @return the name with "m_" prefixed to it + */ + public String getPrefixedName() { + return prefixedName; + } + + /** + * Used internally + * @param name + */ + void setName(String name) { + this.name = name; + this.prefixedName = "m_" + name; + } + + /** + * Returns the value of this material parameter. + * <p> + * Material parameters that are used for material definitions + * will not have a value, unless there's a default value declared + * in the definition. + * + * @return the value of this material parameter. + */ + public Object getValue() { + return value; + } + + /** + * Sets the value of this material parameter. + * <p> + * It is assumed the value is of the same {@link MatParam#getVarType() type} + * as this material parameter. + * + * @param value the value of this material parameter. + */ + public void setValue(Object value) { + this.value = value; + } + + void apply(Renderer r, Technique technique) { + TechniqueDef techDef = technique.getDef(); + if (techDef.isUsingShaders()) { + technique.updateUniformParam(getPrefixedName(), getVarType(), getValue(), true); + } + if (ffBinding != null && r instanceof GL1Renderer) { + ((GL1Renderer) r).setFixedFuncBinding(ffBinding, getValue()); + } + } + + /** + * Returns the material parameter value as it would appear in a J3M + * file. E.g.<br/> + * <code> + * MaterialParameters {<br/> + * ABC : 1 2 3 4<br/> + * }<br/> + * </code> + * Assuming "ABC" is a Vector4 parameter, then the value + * "1 2 3 4" would be returned by this method. + * <br/><br/> + * @return material parameter value as it would appear in a J3M file. + */ + public String getValueAsString() { + switch (type) { + case Boolean: + case Float: + case Int: + return value.toString(); + case Vector2: + Vector2f v2 = (Vector2f) value; + return v2.getX() + " " + v2.getY(); +/* +This may get used at a later point of time +When arrays can be inserted in J3M files + + case Vector2Array: + Vector2f[] v2Arr = (Vector2f[]) value; + String v2str = ""; + for (int i = 0; i < v2Arr.length ; i++) { + v2str += v2Arr[i].getX() + " " + v2Arr[i].getY() + "\n"; + } + return v2str; +*/ + case Vector3: + Vector3f v3 = (Vector3f) value; + return v3.getX() + " " + v3.getY() + " " + v3.getZ(); +/* + case Vector3Array: + Vector3f[] v3Arr = (Vector3f[]) value; + String v3str = ""; + for (int i = 0; i < v3Arr.length ; i++) { + v3str += v3Arr[i].getX() + " " + + v3Arr[i].getY() + " " + + v3Arr[i].getZ() + "\n"; + } + return v3str; + case Vector4Array: + // can be either ColorRGBA, Vector4f or Quaternion + if (value instanceof Vector4f) { + Vector4f[] v4arr = (Vector4f[]) value; + String v4str = ""; + for (int i = 0; i < v4arr.length ; i++) { + v4str += v4arr[i].getX() + " " + + v4arr[i].getY() + " " + + v4arr[i].getZ() + " " + + v4arr[i].getW() + "\n"; + } + return v4str; + } else if (value instanceof ColorRGBA) { + ColorRGBA[] colorArr = (ColorRGBA[]) value; + String colStr = ""; + for (int i = 0; i < colorArr.length ; i++) { + colStr += colorArr[i].getRed() + " " + + colorArr[i].getGreen() + " " + + colorArr[i].getBlue() + " " + + colorArr[i].getAlpha() + "\n"; + } + return colStr; + } else if (value instanceof Quaternion) { + Quaternion[] quatArr = (Quaternion[]) value; + String quatStr = ""; + for (int i = 0; i < quatArr.length ; i++) { + quatStr += quatArr[i].getX() + " " + + quatArr[i].getY() + " " + + quatArr[i].getZ() + " " + + quatArr[i].getW() + "\n"; + } + return quatStr; + } else { + throw new UnsupportedOperationException("Unexpected Vector4Array type: " + value); + } +*/ + case Vector4: + // can be either ColorRGBA, Vector4f or Quaternion + if (value instanceof Vector4f) { + Vector4f v4 = (Vector4f) value; + return v4.getX() + " " + v4.getY() + " " + + v4.getZ() + " " + v4.getW(); + } else if (value instanceof ColorRGBA) { + ColorRGBA color = (ColorRGBA) value; + return color.getRed() + " " + color.getGreen() + " " + + color.getBlue() + " " + color.getAlpha(); + } else if (value instanceof Quaternion) { + Quaternion quat = (Quaternion) value; + return quat.getX() + " " + quat.getY() + " " + + quat.getZ() + " " + quat.getW(); + } else { + throw new UnsupportedOperationException("Unexpected Vector4 type: " + value); + } + case Texture2D: + case Texture3D: + case TextureArray: + case TextureBuffer: + case TextureCubeMap: + Texture texVal = (Texture) value; + TextureKey texKey = (TextureKey) texVal.getKey(); + if (texKey == null){ + throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M"); + } + + String ret = ""; + if (texKey.isFlipY()) { + ret += "Flip "; + } + if (texVal.getWrap(Texture.WrapAxis.S) == WrapMode.Repeat) { + ret += "Repeat "; + } + + return ret + texKey.getName(); + default: + return null; // parameter type not supported in J3M + } + } + + @Override + public MatParam clone() { + try { + MatParam param = (MatParam) super.clone(); + return param; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(type, "varType", null); + oc.write(name, "name", null); + oc.write(ffBinding, "ff_binding", null); + if (value instanceof Savable) { + Savable s = (Savable) value; + oc.write(s, "value_savable", null); + } else if (value instanceof Float) { + Float f = (Float) value; + oc.write(f.floatValue(), "value_float", 0f); + } else if (value instanceof Integer) { + Integer i = (Integer) value; + oc.write(i.intValue(), "value_int", 0); + } else if (value instanceof Boolean) { + Boolean b = (Boolean) value; + oc.write(b.booleanValue(), "value_bool", false); + } + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + type = ic.readEnum("varType", VarType.class, null); + name = ic.readString("name", null); + ffBinding = ic.readEnum("ff_binding", FixedFuncBinding.class, null); + switch (getVarType()) { + case Boolean: + value = ic.readBoolean("value_bool", false); + break; + case Float: + value = ic.readFloat("value_float", 0f); + break; + case Int: + value = ic.readInt("value_int", 0); + break; + default: + value = ic.readSavable("value_savable", null); + break; + } + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof MatParam)) { + return false; + } + + MatParam otherParam = (MatParam) other; + return otherParam.type == type + && otherParam.name.equals(name); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 17 * hash + (this.type != null ? this.type.hashCode() : 0); + hash = 17 * hash + (this.name != null ? this.name.hashCode() : 0); + return hash; + } + + @Override + public String toString() { + return type.name() + " " + name + " : " + getValueAsString(); + } +} diff --git a/engine/src/core/com/jme3/material/MatParamTexture.java b/engine/src/core/com/jme3/material/MatParamTexture.java new file mode 100644 index 0000000..fc8b469 --- /dev/null +++ b/engine/src/core/com/jme3/material/MatParamTexture.java @@ -0,0 +1,67 @@ +package com.jme3.material; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.Renderer; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import java.io.IOException; + +public class MatParamTexture extends MatParam { + + private Texture texture; + private int unit; + + public MatParamTexture(VarType type, String name, Texture texture, int unit) { + super(type, name, texture, null); + this.texture = texture; + this.unit = unit; + } + + public MatParamTexture() { + } + + public Texture getTextureValue() { + return texture; + } + + public void setTextureValue(Texture value) { + this.value = value; + this.texture = value; + } + + public void setUnit(int unit) { + this.unit = unit; + } + + public int getUnit() { + return unit; + } + + @Override + public void apply(Renderer r, Technique technique) { + TechniqueDef techDef = technique.getDef(); + r.setTexture(getUnit(), getTextureValue()); + if (techDef.isUsingShaders()) { + technique.updateUniformParam(getPrefixedName(), getVarType(), getUnit(), true); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(unit, "texture_unit", -1); + oc.write(texture, "texture", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + unit = ic.readInt("texture_unit", -1); + texture = (Texture) ic.readSavable("texture", null); + } +}
\ No newline at end of file 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); + } + } +} diff --git a/engine/src/core/com/jme3/material/MaterialDef.java b/engine/src/core/com/jme3/material/MaterialDef.java new file mode 100644 index 0000000..e7ec3fc --- /dev/null +++ b/engine/src/core/com/jme3/material/MaterialDef.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * 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. + * + * * 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. + * + * * 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. + * + * 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.AssetManager; +import com.jme3.shader.VarType; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Describes a J3MD (Material definition). + * + * @author Kirill Vainer + */ +public class MaterialDef { + + private static final Logger logger = Logger.getLogger(MaterialDef.class.getName()); + + private String name; + private String assetName; + private AssetManager assetManager; + + private List<TechniqueDef> defaultTechs; + private Map<String, TechniqueDef> techniques; + private Map<String, MatParam> matParams; + + /** + * Serialization only. Do not use. + */ + public MaterialDef(){ + } + + /** + * Creates a new material definition with the given name. + * + * @param assetManager The asset manager to use to load shaders + * @param name The debug name of the material definition + */ + public MaterialDef(AssetManager assetManager, String name){ + this.assetManager = assetManager; + this.name = name; + techniques = new HashMap<String, TechniqueDef>(); + matParams = new HashMap<String, MatParam>(); + defaultTechs = new ArrayList<TechniqueDef>(); + logger.log(Level.INFO, "Loaded material definition: {0}", name); + } + + /** + * Returns the asset key name of the asset from which this material + * definition was loaded. + * + * @return Asset key name of the j3md file + */ + public String getAssetName() { + return assetName; + } + + /** + * Set the asset key name. + * + * @param assetName the asset key name + */ + public void setAssetName(String assetName) { + this.assetName = assetName; + } + + /** + * Returns the AssetManager passed in the constructor. + * + * @return the AssetManager passed in the constructor. + */ + public AssetManager getAssetManager(){ + return assetManager; + } + + /** + * The debug name of the material definition. + * + * @return debug name of the material definition. + */ + public String getName(){ + return name; + } + + /** + * Adds a new material parameter. + * + * @param type Type of the parameter + * @param name Name of the parameter + * @param value Default value of the parameter + * @param ffBinding Fixed function binding for the parameter + */ + public void addMaterialParam(VarType type, String name, Object value, FixedFuncBinding ffBinding) { + matParams.put(name, new MatParam(type, name, value, ffBinding)); + } + + /** + * Returns the material parameter with the given name. + * + * @param name The name of the parameter to retrieve + * + * @return The material parameter, or null if it does not exist. + */ + public MatParam getMaterialParam(String name){ + return matParams.get(name); + } + + /** + * Returns a collection of all material parameters declared in this + * material definition. + * <p> + * Modifying the material parameters or the collection will lead + * to undefined results. + * + * @return All material parameters declared in this definition. + */ + public Collection<MatParam> getMaterialParams(){ + return matParams.values(); + } + + /** + * Adds a new technique definition to this material definition. + * <p> + * If the technique name is "Default", it will be added + * to the list of {@link MaterialDef#getDefaultTechniques() default techniques}. + * + * @param technique The technique definition to add. + */ + public void addTechniqueDef(TechniqueDef technique){ + if (technique.getName().equals("Default")){ + defaultTechs.add(technique); + }else{ + techniques.put(technique.getName(), technique); + } + } + + /** + * Returns a list of all default techniques. + * + * @return a list of all default techniques. + */ + public List<TechniqueDef> getDefaultTechniques(){ + return defaultTechs; + } + + /** + * Returns a technique definition with the given name. + * This does not include default techniques which can be + * retrieved via {@link MaterialDef#getDefaultTechniques() }. + * + * @param name The name of the technique definition to find + * + * @return The technique definition, or null if cannot be found. + */ + public TechniqueDef getTechniqueDef(String name) { + return techniques.get(name); + } + +} diff --git a/engine/src/core/com/jme3/material/MaterialList.java b/engine/src/core/com/jme3/material/MaterialList.java new file mode 100644 index 0000000..9f2a512 --- /dev/null +++ b/engine/src/core/com/jme3/material/MaterialList.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * 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. + * + * * 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. + * + * * 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. + * + * 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 java.util.HashMap; + +/** + * A map from material name to a material. Used by loaders to locate + * materials for meshes inside a model. + * + * @author Kirill Vainer + */ +public class MaterialList extends HashMap<String, Material> { +} diff --git a/engine/src/core/com/jme3/material/RenderState.java b/engine/src/core/com/jme3/material/RenderState.java new file mode 100644 index 0000000..37897fd --- /dev/null +++ b/engine/src/core/com/jme3/material/RenderState.java @@ -0,0 +1,1070 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * 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. + * + * * 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. + * + * * 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. + * + * 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.export.*; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import java.io.IOException; + +/** + * <code>RenderState</code> specifies material rendering properties that cannot + * be controlled by a shader on a {@link Material}. The properties + * allow manipulation of rendering features such as depth testing, alpha blending, + * face culling, stencil operations, and much more. + * + * @author Kirill Vainer + */ +public class RenderState implements Cloneable, Savable { + + /** + * The <code>DEFAULT</code> render state is the one used by default + * on all materials unless changed otherwise by the user. + * + * <p> + * It has the following properties: + * <ul> + * <li>Back Face Culling</li> + * <li>Depth Testing Enabled</li> + * <li>Depth Writing Enabled</li> + * </ul> + */ + public static final RenderState DEFAULT = new RenderState(); + + /** + * The <code>NULL</code> render state is identical to the {@link RenderState#DEFAULT} + * render state except that depth testing and face culling are disabled. + */ + public static final RenderState NULL = new RenderState(); + + /** + * The <code>ADDITIONAL</code> render state is identical to the + * {@link RenderState#DEFAULT} render state except that all apply + * values are set to false. This allows the <code>ADDITIONAL</code> render + * state to be combined with other state but only influencing values + * that were changed from the original. + */ + public static final RenderState ADDITIONAL = new RenderState(); + + /** + * <code>TestFunction</code> specifies the testing function for stencil test + * function and alpha test function. + * + * <p>The functions work similarly as described except that for stencil + * test function, the reference value given in the stencil command is + * the input value while the reference is the value already in the stencil + * buffer. + */ + public enum TestFunction { + + /** + * The test always fails + */ + Never, + /** + * The test succeeds if the input value is equal to the reference value. + */ + Equal, + /** + * The test succeeds if the input value is less than the reference value. + */ + Less, + /** + * The test succeeds if the input value is less than or equal to + * the reference value. + */ + LessOrEqual, + /** + * The test succeeds if the input value is greater than the reference value. + */ + Greater, + /** + * The test succeeds if the input value is greater than or equal to + * the reference value. + */ + GreaterOrEqual, + /** + * The test succeeds if the input value does not equal the + * reference value. + */ + NotEqual, + /** + * The test always passes + */ + Always,} + + /** + * <code>BlendMode</code> specifies the blending operation to use. + * + * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode) + */ + public enum BlendMode { + + /** + * No blending mode is used. + */ + Off, + /** + * Additive blending. For use with glows and particle emitters. + * <p> + * Result = Source Color + Destination Color -> (GL_ONE, GL_ONE) + */ + Additive, + /** + * Premultiplied alpha blending, for use with premult alpha textures. + * <p> + * Result = Source Color + (Dest Color * (1 - Source Alpha) ) -> (GL_ONE, GL_ONE_MINUS_SRC_ALPHA) + */ + PremultAlpha, + /** + * Additive blending that is multiplied with source alpha. + * For use with glows and particle emitters. + * <p> + * Result = (Source Alpha * Source Color) + Dest Color -> (GL_SRC_ALPHA, GL_ONE) + */ + AlphaAdditive, + /** + * Color blending, blends in color from dest color + * using source color. + * <p> + * Result = Source Color + (1 - Source Color) * Dest Color -> (GL_ONE, GL_ONE_MINUS_SRC_COLOR) + */ + Color, + /** + * Alpha blending, interpolates to source color from dest color + * using source alpha. + * <p> + * Result = Source Alpha * Source Color + + * (1 - Source Alpha) * Dest Color -> (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + */ + Alpha, + /** + * Multiplies the source and dest colors. + * <p> + * Result = Source Color * Dest Color -> (GL_DST_COLOR, GL_ZERO) + */ + Modulate, + /** + * Multiplies the source and dest colors then doubles the result. + * <p> + * Result = 2 * Source Color * Dest Color -> (GL_DST_COLOR, GL_SRC_COLOR) + */ + ModulateX2 + } + + /** + * <code>FaceCullMode</code> specifies the criteria for faces to be culled. + * + * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode) + */ + public enum FaceCullMode { + + /** + * Face culling is disabled. + */ + Off, + /** + * Cull front faces + */ + Front, + /** + * Cull back faces + */ + Back, + /** + * Cull both front and back faces. + */ + FrontAndBack + } + + /** + * <code>StencilOperation</code> specifies the stencil operation to use + * in a certain scenario as specified in {@link RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilFunction, + * com.jme3.material.RenderState.StencilFunction)} + */ + public enum StencilOperation { + + /** + * Keep the current value. + */ + Keep, + /** + * Set the value to 0 + */ + Zero, + /** + * Replace the value in the stencil buffer with the reference value. + */ + Replace, + + /** + * Increment the value in the stencil buffer, clamp once reaching + * the maximum value. + */ + Increment, + + /** + * Increment the value in the stencil buffer and wrap to 0 when + * reaching the maximum value. + */ + IncrementWrap, + /** + * Decrement the value in the stencil buffer and clamp once reaching 0. + */ + Decrement, + /** + * Decrement the value in the stencil buffer and wrap to the maximum + * value when reaching 0. + */ + DecrementWrap, + + /** + * Does a bitwise invert of the value in the stencil buffer. + */ + Invert + } + + static { + NULL.cullMode = FaceCullMode.Off; + NULL.depthTest = false; + } + + static { + ADDITIONAL.applyPointSprite = false; + ADDITIONAL.applyWireFrame = false; + ADDITIONAL.applyCullMode = false; + ADDITIONAL.applyDepthWrite = false; + ADDITIONAL.applyDepthTest = false; + ADDITIONAL.applyColorWrite = false; + ADDITIONAL.applyBlendMode = false; + ADDITIONAL.applyAlphaTest = false; + ADDITIONAL.applyAlphaFallOff = false; + ADDITIONAL.applyPolyOffset = false; + } + + boolean pointSprite = false; + boolean applyPointSprite = true; + + boolean wireframe = false; + boolean applyWireFrame = true; + + FaceCullMode cullMode = FaceCullMode.Back; + boolean applyCullMode = true; + + boolean depthWrite = true; + boolean applyDepthWrite = true; + + boolean depthTest = true; + boolean applyDepthTest = true; + + boolean colorWrite = true; + boolean applyColorWrite = true; + + BlendMode blendMode = BlendMode.Off; + boolean applyBlendMode = true; + + boolean alphaTest = false; + boolean applyAlphaTest = true; + + float alphaFallOff = 0; + boolean applyAlphaFallOff = true; + + float offsetFactor = 0; + float offsetUnits = 0; + boolean offsetEnabled = false; + boolean applyPolyOffset = true; + + boolean stencilTest = false; + boolean applyStencilTest = false; + StencilOperation frontStencilStencilFailOperation = StencilOperation.Keep; + StencilOperation frontStencilDepthFailOperation = StencilOperation.Keep; + StencilOperation frontStencilDepthPassOperation = StencilOperation.Keep; + StencilOperation backStencilStencilFailOperation = StencilOperation.Keep; + StencilOperation backStencilDepthFailOperation = StencilOperation.Keep; + StencilOperation backStencilDepthPassOperation = StencilOperation.Keep; + TestFunction frontStencilFunction = TestFunction.Always; + TestFunction backStencilFunction = TestFunction.Always; + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(pointSprite, "pointSprite", false); + oc.write(wireframe, "wireframe", false); + oc.write(cullMode, "cullMode", FaceCullMode.Back); + oc.write(depthWrite, "depthWrite", true); + oc.write(depthTest, "depthTest", true); + oc.write(colorWrite, "colorWrite", true); + oc.write(blendMode, "blendMode", BlendMode.Off); + oc.write(alphaTest, "alphaTest", false); + oc.write(alphaFallOff, "alphaFallOff", 0); + oc.write(offsetEnabled, "offsetEnabled", false); + oc.write(offsetFactor, "offsetFactor", 0); + oc.write(offsetUnits, "offsetUnits", 0); + oc.write(stencilTest, "stencilTest", false); + oc.write(frontStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep); + oc.write(frontStencilDepthFailOperation, "frontStencilDepthFailOperation", StencilOperation.Keep); + oc.write(frontStencilDepthPassOperation, "frontStencilDepthPassOperation", StencilOperation.Keep); + oc.write(backStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep); + oc.write(backStencilDepthFailOperation, "backStencilDepthFailOperation", StencilOperation.Keep); + oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep); + oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always); + oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always); + + // Only "additional render state" has them set to false by default + oc.write(applyPointSprite, "applyPointSprite", true); + oc.write(applyWireFrame, "applyWireFrame", true); + oc.write(applyCullMode, "applyCullMode", true); + oc.write(applyDepthWrite, "applyDepthWrite", true); + oc.write(applyDepthTest, "applyDepthTest", true); + oc.write(applyColorWrite, "applyColorWrite", true); + oc.write(applyBlendMode, "applyBlendMode", true); + oc.write(applyAlphaTest, "applyAlphaTest", true); + oc.write(applyAlphaFallOff, "applyAlphaFallOff", true); + oc.write(applyPolyOffset, "applyPolyOffset", true); + + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + pointSprite = ic.readBoolean("pointSprite", false); + wireframe = ic.readBoolean("wireframe", false); + cullMode = ic.readEnum("cullMode", FaceCullMode.class, FaceCullMode.Back); + depthWrite = ic.readBoolean("depthWrite", true); + depthTest = ic.readBoolean("depthTest", true); + colorWrite = ic.readBoolean("colorWrite", true); + blendMode = ic.readEnum("blendMode", BlendMode.class, BlendMode.Off); + alphaTest = ic.readBoolean("alphaTest", false); + alphaFallOff = ic.readFloat("alphaFallOff", 0); + offsetEnabled = ic.readBoolean("offsetEnabled", false); + offsetFactor = ic.readFloat("offsetFactor", 0); + offsetUnits = ic.readFloat("offsetUnits", 0); + stencilTest = ic.readBoolean("stencilTest", false); + frontStencilStencilFailOperation = ic.readEnum("frontStencilStencilFailOperation", StencilOperation.class, StencilOperation.Keep); + frontStencilDepthFailOperation = ic.readEnum("frontStencilDepthFailOperation", StencilOperation.class, StencilOperation.Keep); + frontStencilDepthPassOperation = ic.readEnum("frontStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep); + backStencilStencilFailOperation = ic.readEnum("backStencilStencilFailOperation", StencilOperation.class, StencilOperation.Keep); + backStencilDepthFailOperation = ic.readEnum("backStencilDepthFailOperation", StencilOperation.class, StencilOperation.Keep); + backStencilDepthPassOperation = ic.readEnum("backStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep); + frontStencilFunction = ic.readEnum("frontStencilFunction", TestFunction.class, TestFunction.Always); + backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always); + + applyPointSprite = ic.readBoolean("applyPointSprite", true); + applyWireFrame = ic.readBoolean("applyWireFrame", true); + applyCullMode = ic.readBoolean("applyCullMode", true); + applyDepthWrite = ic.readBoolean("applyDepthWrite", true); + applyDepthTest = ic.readBoolean("applyDepthTest", true); + applyColorWrite = ic.readBoolean("applyColorWrite", true); + applyBlendMode = ic.readBoolean("applyBlendMode", true); + applyAlphaTest = ic.readBoolean("applyAlphaTest", true); + applyAlphaFallOff = ic.readBoolean("applyAlphaFallOff", true); + applyPolyOffset = ic.readBoolean("applyPolyOffset", true); + } + + /** + * Create a clone of this <code>RenderState</code> + * + * @return Clone of this render state. + */ + @Override + public RenderState clone() { + try { + return (RenderState) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * Enables point sprite mode. + * + * <p>When point sprite is enabled, any meshes + * with the type of {@link Mode#Points} will be rendered as 2D quads + * with texturing enabled. Fragment shaders can write to the + * <code>gl_PointCoord</code> variable to manipulate the texture coordinate + * for each pixel. The size of the 2D quad can be controlled by writing + * to the <code>gl_PointSize</code> variable in the vertex shader. + * + * @param pointSprite Enables Point Sprite mode. + */ + public void setPointSprite(boolean pointSprite) { + applyPointSprite = true; + this.pointSprite = pointSprite; + } + + /** + * Sets the alpha fall off value for alpha testing. + * + * <p>If the pixel's alpha value is greater than the + * <code>alphaFallOff</code> then the pixel will be rendered, otherwise + * the pixel will be discarded. + * + * @param alphaFallOff The alpha of all rendered pixels must be higher + * than this value to be rendered. This value should be between 0 and 1. + * + * @see RenderState#setAlphaTest(boolean) + */ + public void setAlphaFallOff(float alphaFallOff) { + applyAlphaFallOff = true; + this.alphaFallOff = alphaFallOff; + } + + /** + * Enable alpha testing. + * + * <p>When alpha testing is enabled, all input pixels' alpha are compared + * to the {@link RenderState#setAlphaFallOff(float) constant alpha falloff}. + * If the input alpha is greater than the falloff, the pixel will be rendered, + * otherwise it will be discarded. + * + * @param alphaTest Set to true to enable alpha testing. + * + * @see RenderState#setAlphaFallOff(float) + */ + public void setAlphaTest(boolean alphaTest) { + applyAlphaTest = true; + this.alphaTest = alphaTest; + } + + /** + * Enable writing color. + * + * <p>When color write is enabled, the result of a fragment shader, the + * <code>gl_FragColor</code>, will be rendered into the color buffer + * (including alpha). + * + * @param colorWrite Set to true to enable color writing. + */ + public void setColorWrite(boolean colorWrite) { + applyColorWrite = true; + this.colorWrite = colorWrite; + } + + /** + * Set the face culling mode. + * + * <p>See the {@link FaceCullMode} enum on what each value does. + * Face culling will project the triangle's points onto the screen + * and determine if the triangle is in counter-clockwise order or + * clockwise order. If a triangle is in counter-clockwise order, then + * it is considered a front-facing triangle, otherwise, it is considered + * a back-facing triangle. + * + * @param cullMode the face culling mode. + */ + public void setFaceCullMode(FaceCullMode cullMode) { + applyCullMode = true; + this.cullMode = cullMode; + } + + /** + * Set the blending mode. + * + * <p>When blending is enabled, (<code>blendMode</code> is not {@link BlendMode#Off}) + * the input pixel will be blended with the pixel + * already in the color buffer. The blending operation is determined + * by the {@link BlendMode}. For example, the {@link BlendMode#Additive} + * will add the input pixel's color to the color already in the color buffer: + * <br/> + * <code>Result = Source Color + Destination Color</code> + * + * @param blendMode The blend mode to use. Set to {@link BlendMode#Off} + * to disable blending. + */ + public void setBlendMode(BlendMode blendMode) { + applyBlendMode = true; + this.blendMode = blendMode; + } + + /** + * Enable depth testing. + * + * <p>When depth testing is enabled, a pixel must pass the depth test + * before it is written to the color buffer. + * The input pixel's depth value must be less than or equal than + * the value already in the depth buffer to pass the depth test. + * + * @param depthTest Enable or disable depth testing. + */ + public void setDepthTest(boolean depthTest) { + applyDepthTest = true; + this.depthTest = depthTest; + } + + /** + * Enable depth writing. + * + * <p>After passing the {@link RenderState#setDepthTest(boolean) depth test}, + * a pixel's depth value will be written into the depth buffer if + * depth writing is enabled. + * + * @param depthWrite True to enable writing to the depth buffer. + */ + public void setDepthWrite(boolean depthWrite) { + applyDepthWrite = true; + this.depthWrite = depthWrite; + } + + /** + * Enables wireframe rendering mode. + * + * <p>When in wireframe mode, {@link Mesh meshes} rendered in triangle mode + * will not be solid, but instead, only the edges of the triangles + * will be rendered. + * + * @param wireframe True to enable wireframe mode. + */ + public void setWireframe(boolean wireframe) { + applyWireFrame = true; + this.wireframe = wireframe; + } + + /** + * Offsets the on-screen z-order of the material's polygons, to combat visual artefacts like + * stitching, bleeding and z-fighting for overlapping polygons. + * Factor and units are summed to produce the depth offset. + * This offset is applied in screen space, + * typically with positive Z pointing into the screen. + * Typical values are (1.0f, 1.0f) or (-1.0f, -1.0f) + * + * @see <a href="http://www.opengl.org/resources/faq/technical/polygonoffset.htm" rel="nofollow">http://www.opengl.org/resources/faq/technical/polygonoffset.htm</a> + * @param factor scales the maximum Z slope, with respect to X or Y of the polygon + * @param units scales the minimum resolvable depth buffer value + **/ + public void setPolyOffset(float factor, float units) { + applyPolyOffset = true; + offsetEnabled = true; + offsetFactor = factor; + offsetUnits = units; + } + + /** + * Enable stencil testing. + * + * <p>Stencil testing can be used to filter pixels according to the stencil + * buffer. Objects can be rendered with some stencil operation to manipulate + * the values in the stencil buffer, then, other objects can be rendered + * to test against the values written previously. + * + * @param enabled Set to true to enable stencil functionality. If false + * all other parameters are ignored. + * + * @param _frontStencilStencilFailOperation Sets the operation to occur when + * a front-facing triangle fails the front stencil function. + * @param _frontStencilDepthFailOperation Sets the operation to occur when + * a front-facing triangle fails the depth test. + * @param _frontStencilDepthPassOperation Set the operation to occur when + * a front-facing triangle passes the depth test. + * @param _backStencilStencilFailOperation Set the operation to occur when + * a back-facing triangle fails the back stencil function. + * @param _backStencilDepthFailOperation Set the operation to occur when + * a back-facing triangle fails the depth test. + * @param _backStencilDepthPassOperation Set the operation to occur when + * a back-facing triangle passes the depth test. + * @param _frontStencilFunction Set the test function for front-facing triangles. + * @param _backStencilFunction Set the test function for back-facing triangles. + */ + public void setStencil(boolean enabled, + StencilOperation _frontStencilStencilFailOperation, + StencilOperation _frontStencilDepthFailOperation, + StencilOperation _frontStencilDepthPassOperation, + StencilOperation _backStencilStencilFailOperation, + StencilOperation _backStencilDepthFailOperation, + StencilOperation _backStencilDepthPassOperation, + TestFunction _frontStencilFunction, + TestFunction _backStencilFunction) { + + stencilTest = enabled; + applyStencilTest = true; + this.frontStencilStencilFailOperation = _frontStencilStencilFailOperation; + this.frontStencilDepthFailOperation = _frontStencilDepthFailOperation; + this.frontStencilDepthPassOperation = _frontStencilDepthPassOperation; + this.backStencilStencilFailOperation = _backStencilStencilFailOperation; + this.backStencilDepthFailOperation = _backStencilDepthFailOperation; + this.backStencilDepthPassOperation = _backStencilDepthPassOperation; + this.frontStencilFunction = _frontStencilFunction; + this.backStencilFunction = _backStencilFunction; + } + + /** + * Check if stencil test is enabled. + * + * @return True if stencil test is enabled. + */ + public boolean isStencilTest() { + return stencilTest; + } + + /** + * Retrieve the front stencil fail operation. + * + * @return the front stencil fail operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getFrontStencilStencilFailOperation() { + return frontStencilStencilFailOperation; + } + + /** + * Retrieve the front depth test fail operation. + * + * @return the front depth test fail operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getFrontStencilDepthFailOperation() { + return frontStencilDepthFailOperation; + } + + /** + * Retrieve the front depth test pass operation. + * + * @return the front depth test pass operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getFrontStencilDepthPassOperation() { + return frontStencilDepthPassOperation; + } + + /** + * Retrieve the back stencil fail operation. + * + * @return the back stencil fail operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getBackStencilStencilFailOperation() { + return backStencilStencilFailOperation; + } + + /** + * Retrieve the back depth test fail operation. + * + * @return the back depth test fail operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getBackStencilDepthFailOperation() { + return backStencilDepthFailOperation; + } + + /** + * Retrieve the back depth test pass operation. + * + * @return the back depth test pass operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getBackStencilDepthPassOperation() { + return backStencilDepthPassOperation; + } + + /** + * Retrieve the front stencil function. + * + * @return the front stencil function. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public TestFunction getFrontStencilFunction() { + return frontStencilFunction; + } + + /** + * Retrieve the back stencil function. + * + * @return the back stencil function. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public TestFunction getBackStencilFunction() { + return backStencilFunction; + } + + /** + * Retrieve the blend mode. + * + * @return the blend mode. + */ + public BlendMode getBlendMode() { + return blendMode; + } + + /** + * Check if point sprite mode is enabled + * + * @return True if point sprite mode is enabled. + * + * @see RenderState#setPointSprite(boolean) + */ + public boolean isPointSprite() { + return pointSprite; + } + + /** + * Check if alpha test is enabled. + * + * @return True if alpha test is enabled. + * + * @see RenderState#setAlphaTest(boolean) + */ + public boolean isAlphaTest() { + return alphaTest; + } + + /** + * Retrieve the face cull mode. + * + * @return the face cull mode. + * + * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode) + */ + public FaceCullMode getFaceCullMode() { + return cullMode; + } + + /** + * Check if depth test is enabled. + * + * @return True if depth test is enabled. + * + * @see RenderState#setDepthTest(boolean) + */ + public boolean isDepthTest() { + return depthTest; + } + + /** + * Check if depth write is enabled. + * + * @return True if depth write is enabled. + * + * @see RenderState#setDepthWrite(boolean) + */ + public boolean isDepthWrite() { + return depthWrite; + } + + /** + * Check if wireframe mode is enabled. + * + * @return True if wireframe mode is enabled. + * + * @see RenderState#setWireframe(boolean) + */ + public boolean isWireframe() { + return wireframe; + } + + /** + * Check if color writing is enabled. + * + * @return True if color writing is enabled. + * + * @see RenderState#setColorWrite(boolean) + */ + public boolean isColorWrite() { + return colorWrite; + } + + /** + * Retrieve the poly offset factor value. + * + * @return the poly offset factor value. + * + * @see RenderState#setPolyOffset(float, float) + */ + public float getPolyOffsetFactor() { + return offsetFactor; + } + + /** + * Retrieve the poly offset units value. + * + * @return the poly offset units value. + * + * @see RenderState#setPolyOffset(float, float) + */ + public float getPolyOffsetUnits() { + return offsetUnits; + } + + /** + * Check if polygon offset is enabled. + * + * @return True if polygon offset is enabled. + * + * @see RenderState#setPolyOffset(float, float) + */ + public boolean isPolyOffset() { + return offsetEnabled; + } + + /** + * Retrieve the alpha falloff value. + * + * @return the alpha falloff value. + * + * @see RenderState#setAlphaFallOff(float) + */ + public float getAlphaFallOff() { + return alphaFallOff; + } + + public boolean isApplyAlphaFallOff() { + return applyAlphaFallOff; + } + + public boolean isApplyAlphaTest() { + return applyAlphaTest; + } + + public boolean isApplyBlendMode() { + return applyBlendMode; + } + + public boolean isApplyColorWrite() { + return applyColorWrite; + } + + public boolean isApplyCullMode() { + return applyCullMode; + } + + public boolean isApplyDepthTest() { + return applyDepthTest; + } + + public boolean isApplyDepthWrite() { + return applyDepthWrite; + } + + public boolean isApplyPointSprite() { + return applyPointSprite; + } + + public boolean isApplyPolyOffset() { + return applyPolyOffset; + } + + public boolean isApplyWireFrame() { + return applyWireFrame; + } + + /** + * Merges <code>this</code> state and <code>additionalState</code> into + * the parameter <code>state</code> based on a specific criteria. + * + * <p>The criteria for this merge is the following:<br/> + * For every given property, such as alpha test or depth write, check + * if it was modified from the original in the <code>additionalState</code> + * if it was modified, then copy the property from the <code>additionalState</code> + * into the parameter <code>state</code>, otherwise, copy the property from <code>this</code> + * into the parameter <code>state</code>. If <code>additionalState</code> + * is <code>null</code>, then no modifications are made and <code>this</code> is returned, + * otherwise, the parameter <code>state</code> is returned with the result + * of the merge. + * + * @param additionalState The <code>additionalState</code>, from which data is taken only + * if it was modified by the user. + * @param state Contains output of the method if <code>additionalState</code> + * is not null. + * @return <code>state</code> if <code>additionalState</code> is non-null, + * otherwise returns <code>this</code> + */ + public RenderState copyMergedTo(RenderState additionalState, RenderState state) { + if (additionalState == null) { + return this; + } + + if (additionalState.applyPointSprite) { + state.pointSprite = additionalState.pointSprite; + } else { + state.pointSprite = pointSprite; + } + if (additionalState.applyWireFrame) { + state.wireframe = additionalState.wireframe; + } else { + state.wireframe = wireframe; + } + + if (additionalState.applyCullMode) { + state.cullMode = additionalState.cullMode; + } else { + state.cullMode = cullMode; + } + if (additionalState.applyDepthWrite) { + state.depthWrite = additionalState.depthWrite; + } else { + state.depthWrite = depthWrite; + } + if (additionalState.applyDepthTest) { + state.depthTest = additionalState.depthTest; + } else { + state.depthTest = depthTest; + } + if (additionalState.applyColorWrite) { + state.colorWrite = additionalState.colorWrite; + } else { + state.colorWrite = colorWrite; + } + if (additionalState.applyBlendMode) { + state.blendMode = additionalState.blendMode; + } else { + state.blendMode = blendMode; + } + if (additionalState.applyAlphaTest) { + state.alphaTest = additionalState.alphaTest; + } else { + state.alphaTest = alphaTest; + } + + if (additionalState.applyAlphaFallOff) { + state.alphaFallOff = additionalState.alphaFallOff; + } else { + state.alphaFallOff = alphaFallOff; + } + if (additionalState.applyPolyOffset) { + state.offsetEnabled = additionalState.offsetEnabled; + state.offsetFactor = additionalState.offsetFactor; + state.offsetUnits = additionalState.offsetUnits; + } else { + state.offsetEnabled = offsetEnabled; + state.offsetFactor = offsetFactor; + state.offsetUnits = offsetUnits; + } + if (additionalState.applyStencilTest){ + state.stencilTest = additionalState.stencilTest; + + state.frontStencilStencilFailOperation = additionalState.frontStencilStencilFailOperation; + state.frontStencilDepthFailOperation = additionalState.frontStencilDepthFailOperation; + state.frontStencilDepthPassOperation = additionalState.frontStencilDepthPassOperation; + + state.backStencilStencilFailOperation = additionalState.backStencilStencilFailOperation; + state.backStencilDepthFailOperation = additionalState.backStencilDepthFailOperation; + state.backStencilDepthPassOperation = additionalState.backStencilDepthPassOperation; + + state.frontStencilFunction = additionalState.frontStencilFunction; + state.backStencilFunction = additionalState.backStencilFunction; + }else{ + state.stencilTest = stencilTest; + + state.frontStencilStencilFailOperation = frontStencilStencilFailOperation; + state.frontStencilDepthFailOperation = frontStencilDepthFailOperation; + state.frontStencilDepthPassOperation = frontStencilDepthPassOperation; + + state.backStencilStencilFailOperation = backStencilStencilFailOperation; + state.backStencilDepthFailOperation = backStencilDepthFailOperation; + state.backStencilDepthPassOperation = backStencilDepthPassOperation; + + state.frontStencilFunction = frontStencilFunction; + state.backStencilFunction = backStencilFunction; + } + return state; + } + + @Override + public String toString() { + return "RenderState[\n" + "pointSprite=" + pointSprite + "\napplyPointSprite=" + applyPointSprite + "\nwireframe=" + wireframe + "\napplyWireFrame=" + applyWireFrame + "\ncullMode=" + cullMode + "\napplyCullMode=" + applyCullMode + "\ndepthWrite=" + depthWrite + "\napplyDepthWrite=" + applyDepthWrite + "\ndepthTest=" + depthTest + "\napplyDepthTest=" + applyDepthTest + "\ncolorWrite=" + colorWrite + "\napplyColorWrite=" + applyColorWrite + "\nblendMode=" + blendMode + "\napplyBlendMode=" + applyBlendMode + "\nalphaTest=" + alphaTest + "\napplyAlphaTest=" + applyAlphaTest + "\nalphaFallOff=" + alphaFallOff + "\napplyAlphaFallOff=" + applyAlphaFallOff + "\noffsetEnabled=" + offsetEnabled + "\napplyPolyOffset=" + applyPolyOffset + "\noffsetFactor=" + offsetFactor + "\noffsetUnits=" + offsetUnits + "\n]"; + } +} diff --git a/engine/src/core/com/jme3/material/Technique.java b/engine/src/core/com/jme3/material/Technique.java new file mode 100644 index 0000000..5ae20a5 --- /dev/null +++ b/engine/src/core/com/jme3/material/Technique.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * 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. + * + * * 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. + * + * * 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. + * + * 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.AssetManager; +import com.jme3.export.*; +import com.jme3.shader.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Logger; + +/** + * Represents a technique instance. + */ +public class Technique implements Savable { + + private static final Logger logger = Logger.getLogger(Technique.class.getName()); + private TechniqueDef def; + private Material owner; + private ArrayList<Uniform> worldBindUniforms; + private DefineList defines; + private Shader shader; + private boolean needReload = true; + + /** + * Creates a new technique instance that implements the given + * technique definition. + * + * @param owner The material that will own this technique + * @param def The technique definition being implemented. + */ + public Technique(Material owner, TechniqueDef def) { + this.owner = owner; + this.def = def; + if (def.isUsingShaders()) { + this.worldBindUniforms = new ArrayList<Uniform>(); + this.defines = new DefineList(); + } + } + + /** + * Serialization only. Do not use. + */ + public Technique() { + } + + /** + * Returns the technique definition that is implemented by this technique + * instance. + * + * @return the technique definition that is implemented by this technique + * instance. + */ + public TechniqueDef getDef() { + return def; + } + + /** + * Returns the shader currently used by this technique instance. + * <p> + * Shaders are typically loaded dynamically when the technique is first + * used, therefore, this variable will most likely be null most of the time. + * + * @return the shader currently used by this technique instance. + */ + public Shader getShader() { + return shader; + } + + /** + * Returns a list of uniforms that implements the world parameters + * that were requested by the material definition. + * + * @return a list of uniforms implementing the world parameters. + */ + public List<Uniform> getWorldBindUniforms() { + return worldBindUniforms; + } + + /** + * Called by the material to tell the technique a parameter was modified + */ + void notifySetParam(String paramName, VarType type, Object value) { + String defineName = def.getShaderParamDefine(paramName); + if (defineName != null) { + needReload = defines.set(defineName, type, value); + } + if (shader != null) { + updateUniformParam(paramName, type, value); + } + } + + /** + * Called by the material to tell the technique a parameter was cleared + */ + void notifyClearParam(String paramName) { + String defineName = def.getShaderParamDefine(paramName); + if (defineName != null) { + needReload = defines.remove(defineName); + } + if (shader != null) { + if (!paramName.startsWith("m_")) { + paramName = "m_" + paramName; + } + shader.removeUniform(paramName); + } + } + + void updateUniformParam(String paramName, VarType type, Object value, boolean ifNotOwner) { + Uniform u = shader.getUniform(paramName); + +// if (ifNotOwner && u.getLastChanger() == owner) +// return; + + switch (type) { + case Texture2D: // fall intentional + case Texture3D: + case TextureArray: + case TextureCubeMap: + case Int: + u.setValue(VarType.Int, value); + break; + default: + u.setValue(type, value); + break; + } +// u.setLastChanger(owner); + } + + void updateUniformParam(String paramName, VarType type, Object value) { + updateUniformParam(paramName, type, value, false); + } + + /** + * Returns true if the technique must be reloaded. + * <p> + * If a technique needs to reload, then the {@link Material} should + * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this + * technique. + * + * @return true if the technique must be reloaded. + */ + public boolean isNeedReload() { + return needReload; + } + + /** + * Prepares the technique for use by loading the shader and setting + * the proper defines based on material parameters. + * + * @param assetManager The asset manager to use for loading shaders. + */ + public void makeCurrent(AssetManager assetManager) { + // check if reload is needed.. + if (def.isUsingShaders()) { + DefineList newDefines = new DefineList(); + Collection<MatParam> params = owner.getParams(); + for (MatParam param : params) { + String defineName = def.getShaderParamDefine(param.getName()); + if (defineName != null) { + newDefines.set(defineName, param.getVarType(), param.getValue()); + } + } + + if (!needReload && defines.getCompiled().equals(newDefines.getCompiled())) { + newDefines = null; + // defines have not been changed.. + } else { + defines.clear(); + defines.addFrom(newDefines); + // defines changed, recompile needed + loadShader(assetManager); + } + } + } + + private void loadShader(AssetManager manager) { + // recompute define list + DefineList allDefines = new DefineList(); + allDefines.addFrom(def.getShaderPresetDefines()); + allDefines.addFrom(defines); + + ShaderKey key = new ShaderKey(def.getVertexShaderName(), + def.getFragmentShaderName(), + allDefines, + def.getShaderLanguage()); + shader = manager.loadShader(key); + if (shader == null) { + logger.warning("Failed to reload shader!"); + return; + } + + // refresh the uniform links + //owner.updateUniformLinks(); + + // register the world bound uniforms + worldBindUniforms.clear(); + if (def.getWorldBindings() != null) { + for (UniformBinding binding : def.getWorldBindings()) { + Uniform uniform = shader.getUniform("g_" + binding.name()); + uniform.setBinding(binding); + if (uniform != null) { + worldBindUniforms.add(uniform); + } + } + } + + needReload = false; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(def, "def", null); + // TODO: + // oc.write(owner, "owner", null); + oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null); + oc.write(defines, "defines", null); + oc.write(shader, "shader", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + def = (TechniqueDef) ic.readSavable("def", null); + worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null); + defines = (DefineList) ic.readSavable("defines", null); + shader = (Shader) ic.readSavable("shader", null); + //if (shader != null) + // owner.updateUniformLinks(); + } +} diff --git a/engine/src/core/com/jme3/material/TechniqueDef.java b/engine/src/core/com/jme3/material/TechniqueDef.java new file mode 100644 index 0000000..aaeb340 --- /dev/null +++ b/engine/src/core/com/jme3/material/TechniqueDef.java @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * 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. + * + * * 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. + * + * * 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. + * + * 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.export.*; +import com.jme3.renderer.Caps; +import com.jme3.renderer.Renderer; +import com.jme3.shader.DefineList; +import com.jme3.shader.UniformBinding; +import com.jme3.shader.VarType; +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; + +/** + * Describes a technique definition. + * + * @author Kirill Vainer + */ +public class TechniqueDef implements Savable { + + /** + * Describes light rendering mode. + */ + public enum LightMode { + /** + * Disable light-based rendering + */ + Disable, + + /** + * Enable light rendering by using a single pass. + * <p> + * An array of light positions and light colors is passed to the shader + * containing the world light list for the geometry being rendered. + */ + SinglePass, + + /** + * Enable light rendering by using multi-pass rendering. + * <p> + * The geometry will be rendered once for each light. Each time the + * light position and light color uniforms are updated to contain + * the values for the current light. The ambient light color uniform + * is only set to the ambient light color on the first pass, future + * passes have it set to black. + */ + MultiPass, + + /** + * Enable light rendering by using the + * {@link Renderer#setLighting(com.jme3.light.LightList) renderer's setLighting} + * method. + * <p> + * The specific details of rendering the lighting is up to the + * renderer implementation. + */ + FixedPipeline, + } + + public enum ShadowMode { + Disable, + InPass, + PostPass, + } + + private EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class); + private String name; + + private String vertName; + private String fragName; + private String shaderLang; + private DefineList presetDefines; + private boolean usesShaders; + + private RenderState renderState; + private LightMode lightMode = LightMode.Disable; + private ShadowMode shadowMode = ShadowMode.Disable; + + private HashMap<String, String> defineParams; + private ArrayList<UniformBinding> worldBinds; + + /** + * Creates a new technique definition. + * <p> + * Used internally by the J3M/J3MD loader. + * + * @param name The name of the technique, should be set to <code>null</code> + * for default techniques. + */ + public TechniqueDef(String name){ + this.name = name == null ? "Default" : name; + } + + /** + * Serialization only. Do not use. + */ + public TechniqueDef(){ + } + + /** + * Returns the name of this technique as specified in the J3MD file. + * Default techniques have the name "Default". + * + * @return the name of this technique + */ + public String getName(){ + return name; + } + + /** + * Returns the light mode. + * @return the light mode. + * @see LightMode + */ + public LightMode getLightMode() { + return lightMode; + } + + /** + * Set the light mode + * + * @param lightMode the light mode + * + * @see LightMode + */ + public void setLightMode(LightMode lightMode) { + this.lightMode = lightMode; + } + + /** + * Returns the shadow mode. + * @return the shadow mode. + */ + public ShadowMode getShadowMode() { + return shadowMode; + } + + /** + * Set the shadow mode. + * + * @param shadowMode the shadow mode. + * + * @see ShadowMode + */ + public void setShadowMode(ShadowMode shadowMode) { + this.shadowMode = shadowMode; + } + + /** + * Returns the render state that this technique is using + * @return the render state that this technique is using + * @see #setRenderState(com.jme3.material.RenderState) + */ + public RenderState getRenderState() { + return renderState; + } + + /** + * Sets the render state that this technique is using. + * + * @param renderState the render state that this technique is using. + * + * @see RenderState + */ + public void setRenderState(RenderState renderState) { + this.renderState = renderState; + } + + /** + * Returns true if this technique uses shaders, false otherwise. + * + * @return true if this technique uses shaders, false otherwise. + * + * @see #setShaderFile(java.lang.String, java.lang.String, java.lang.String) + */ + public boolean isUsingShaders(){ + return usesShaders; + } + + /** + * Gets the {@link Caps renderer capabilities} that are required + * by this technique. + * + * @return the required renderer capabilities + */ + public EnumSet<Caps> getRequiredCaps() { + return requiredCaps; + } + + /** + * Sets the shaders that this technique definition will use. + * + * @param vertexShader The name of the vertex shader + * @param fragmentShader The name of the fragment shader + * @param shaderLanguage The shader language + */ + public void setShaderFile(String vertexShader, String fragmentShader, String shaderLanguage){ + this.vertName = vertexShader; + this.fragName = fragmentShader; + this.shaderLang = shaderLanguage; + + Caps langCap = Caps.valueOf(shaderLanguage); + requiredCaps.add(langCap); + + usesShaders = true; + } + + /** + * Returns the define name which the given material parameter influences. + * + * @param paramName The parameter name to look up + * @return The define name + * + * @see #addShaderParamDefine(java.lang.String, java.lang.String) + */ + public String getShaderParamDefine(String paramName){ + if (defineParams == null) + return null; + + return defineParams.get(paramName); + } + + /** + * Adds a define linked to a material parameter. + * <p> + * Any time the material parameter on the parent material is altered, + * the appropriate define on the technique will be modified as well. + * See the method + * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) } + * on the exact details of how the material parameter changes the define. + * + * @param paramName The name of the material parameter to link to. + * @param defineName The name of the define parameter, e.g. USE_LIGHTING + */ + public void addShaderParamDefine(String paramName, String defineName){ + if (defineParams == null) + defineParams = new HashMap<String, String>(); + + defineParams.put(paramName, defineName); + } + + /** + * Returns the {@link DefineList} for the preset defines. + * + * @return the {@link DefineList} for the preset defines. + * + * @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object) + */ + public DefineList getShaderPresetDefines() { + return presetDefines; + } + + /** + * Adds a preset define. + * <p> + * Preset defines do not depend upon any parameters to be activated, + * they are always passed to the shader as long as this technique is used. + * + * @param defineName The name of the define parameter, e.g. USE_LIGHTING + * @param type The type of the define. See + * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) } + * to see why it matters. + * + * @param value The value of the define + */ + public void addShaderPresetDefine(String defineName, VarType type, Object value){ + if (presetDefines == null) + presetDefines = new DefineList(); + + presetDefines.set(defineName, type, value); + } + + /** + * Returns the name of the fragment shader used by the technique, or null + * if no fragment shader is specified. + * + * @return the name of the fragment shader to be used. + */ + public String getFragmentShaderName() { + return fragName; + } + + + /** + * Returns the name of the vertex shader used by the technique, or null + * if no vertex shader is specified. + * + * @return the name of the vertex shader to be used. + */ + public String getVertexShaderName() { + return vertName; + } + + /** + * Returns the shader language of the shaders used in this technique. + * + * @return the shader language of the shaders used in this technique. + */ + public String getShaderLanguage() { + return shaderLang; + } + + /** + * Adds a new world parameter by the given name. + * + * @param name The world parameter to add. + * @return True if the world parameter name was found and added + * to the list of world parameters, false otherwise. + */ + public boolean addWorldParam(String name) { + if (worldBinds == null){ + worldBinds = new ArrayList<UniformBinding>(); + } + + try { + worldBinds.add( UniformBinding.valueOf(name) ); + return true; + } catch (IllegalArgumentException ex){ + return false; + } + } + + /** + * Returns a list of world parameters that are used by this + * technique definition. + * + * @return The list of world parameters + */ + public List<UniformBinding> getWorldBindings() { + return worldBinds; + } + + public void write(JmeExporter ex) throws IOException{ + OutputCapsule oc = ex.getCapsule(this); + oc.write(name, "name", null); + oc.write(vertName, "vertName", null); + oc.write(fragName, "fragName", null); + oc.write(shaderLang, "shaderLang", null); + oc.write(presetDefines, "presetDefines", null); + oc.write(lightMode, "lightMode", LightMode.Disable); + oc.write(shadowMode, "shadowMode", ShadowMode.Disable); + oc.write(renderState, "renderState", null); + oc.write(usesShaders, "usesShaders", false); + // TODO: Finish this when Map<String, String> export is available +// oc.write(defineParams, "defineParams", null); + // TODO: Finish this when List<Enum> export is available +// oc.write(worldBinds, "worldBinds", null); + } + + public void read(JmeImporter im) throws IOException{ + InputCapsule ic = im.getCapsule(this); + name = ic.readString("name", null); + vertName = ic.readString("vertName", null); + fragName = ic.readString("fragName", null); + shaderLang = ic.readString("shaderLang", null); + presetDefines = (DefineList) ic.readSavable("presetDefines", null); + lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable); + shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable); + renderState = (RenderState) ic.readSavable("renderState", null); + usesShaders = ic.readBoolean("usesShaders", false); + } + +} diff --git a/engine/src/core/com/jme3/material/package.html b/engine/src/core/com/jme3/material/package.html new file mode 100644 index 0000000..9af9cc8 --- /dev/null +++ b/engine/src/core/com/jme3/material/package.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> + +<head> +<title></title> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +</head> +<body> + +The <code>com.jme3.material</code> package contains classes for manipulating +jMonkeyEngine materials. +Materials are applied to {@link com.jme3.scene.Geometry geometries} in the +scene. +Each geometry has a single material which is used to render that +geometry. +<p> +Materials (also known as material instances) are extended from +material definitions. + +<h3>Material definitions</h3> +<p> +Material definitions provide the "logic" for the material. Usually a shader that +will handle drawing the object, and corresponding parameters that allow +configuration of the shader. +Material definitions can be created through J3MD files. +The J3MD file abstracts the shader and its configuration away from the user, allowing a +simple interface where one can simply set a few parameters on the material to change its +appearance and the way its handled. + +<h3>Techniques</h3> +<p> +Techniques specify a specific way of rendering a material. Typically +a technique is used to implement the same material for each configuration +of the system. For GPUs that do not support shaders, a "fixed function pipeline" +technique could exist to take care of rendering for that configuration + +<h3>Render states</h3> +<p> +See {@link com.jme3.material.RenderState}. + +<h3>Example Usage</h3> +<p> +Creating a textured material +<code> +// Create a material instance +Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + +// Load the texture. +Texture tex = assetManager.loadTexture("Textures/Test/Test.jpg"); + +// Set the parameters +mat.setTexture("ColorMap", tex); +</code> + + + +</body> +</html> |