Browse Source

* Materials.equals() method now works correctly (checking if material will look the same if rendered, in 99.9% of cases..)
* ListMap entries now implement equals method as required by spec

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9289 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

Sha..rd 13 years ago
parent
commit
ddca06e607

+ 24 - 9
engine/src/core/com/jme3/material/MatParam.java

@@ -328,21 +328,36 @@ When arrays can be inserted in J3M files
     }
 
     @Override
-    public boolean equals(Object other) {
-        if (!(other instanceof MatParam)) {
+    public boolean equals(Object obj) {
+        if (obj == null) {
             return false;
         }
-
-        MatParam otherParam = (MatParam) other;
-        return otherParam.type == type
-                && otherParam.name.equals(name);
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final MatParam other = (MatParam) obj;
+        if (this.type != other.type) {
+            return false;
+        }
+        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
+            return false;
+        }
+        if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) {
+            return false;
+        }
+        if (this.ffBinding != other.ffBinding) {
+            return false;
+        }
+        return true;
     }
 
     @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);
+        int hash = 7;
+        hash = 59 * hash + (this.type != null ? this.type.hashCode() : 0);
+        hash = 59 * hash + (this.name != null ? this.name.hashCode() : 0);
+        hash = 59 * hash + (this.value != null ? this.value.hashCode() : 0);
+        hash = 59 * hash + (this.ffBinding != null ? this.ffBinding.hashCode() : 0);
         return hash;
     }
 

+ 64 - 78
engine/src/core/com/jme3/material/Material.java

@@ -29,7 +29,7 @@
  */
 package com.jme3.material;
 
-import com.jme3.asset.Asset;
+import com.jme3.asset.CloneableSmartAsset;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetManager;
 import com.jme3.export.*;
@@ -63,9 +63,10 @@ import java.util.logging.Logger;
  * Setting the parameters can modify the behavior of a
  * shader.
  * <p/>
+ * 
  * @author Kirill Vainer
  */
-public class Material implements Asset, Cloneable, Savable, Comparable<Material> {
+public class Material implements CloneableSmartAsset, Cloneable, Savable {
 
     // Version #2: Fixed issue with RenderState.apply*** flags not getting exported
     public static final int SAVABLE_VERSION = 2;
@@ -139,7 +140,7 @@ public class Material implements Asset, Cloneable, Savable, Comparable<Material>
     public String getName() {
         return name;
     }
-
+    
     /**
      * This method sets the name of the material.
      * The name is not the same as the asset name.
@@ -188,27 +189,6 @@ public class Material implements Asset, Cloneable, Savable, Comparable<Material>
         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.
      */
@@ -237,77 +217,84 @@ public class Material implements Asset, Cloneable, Savable, Comparable<Material>
 
     /**
      * Compares two materials and returns true if they are equal.
-     * This methods compare definition,  params, additional render states
-     * The difference with the equals method is that it's truely comaring the objects. (equals compare the sort ids for geometry sorting)
-     * @param mat the material to cmpare to this material
+     * This methods compare definition, parameters, additional render states
+     * 
+     * @param otherObj the material to compare to this material
      * @return true if the materials are equal.
      */
-    public boolean isEqual(Material mat) {
-        //early exit if the material are the same object
-        if (this == mat) {
+    @Override
+    public boolean equals(Object otherObj) {
+        if (!(otherObj instanceof Material)) {
+            return false;
+        }
+        
+        Material other = (Material) otherObj;
+        
+        // Early exit if the material are the same object
+        if (this == other) {
             return true;
         }
 
-
-        //comparing matrial definition        
-        if (this.getMaterialDef() != mat.getMaterialDef()) {
+        // Check material definition        
+        if (this.getMaterialDef() != other.getMaterialDef()) {
             return false;
         }
 
-        //early exit if the size of the params is different
-        if (paramValues.size() != mat.paramValues.size()) {
+        // Early exit if the size of the params is different
+        if (this.paramValues.size() != other.paramValues.size()) {
             return false;
         }
-
-        //comparing params
-        for (String paramKey : paramValues.keySet()) {
-            MatParam paramOrig = getParam(paramKey);
-            MatParam param = mat.getParam(paramKey);
-            //this param does not exist in compared mat
-            if (param == null) {
+        
+        // Checking technique
+        if (this.technique != null || other.technique != null) {
+            // Techniques are considered equal if their names are the same
+            // E.g. if user chose custom technique for one material but 
+            // uses default technique for other material, the materials 
+            // are not equal.
+            String thisDefName = this.technique != null ? this.technique.getDef().getName() : null;
+            String otherDefName = other.technique != null ? other.technique.getDef().getName() : null;
+            if (!thisDefName.equals(otherDefName)) {
                 return false;
             }
+        }
+
+        // Comparing parameters
+        for (String paramKey : paramValues.keySet()) {
+            MatParam thisParam = this.getParam(paramKey);
+            MatParam otherParam = other.getParam(paramKey);
 
-            //params exist in both materials but they not of the same type
-            if (param.type != paramOrig.type) {
+            // This param does not exist in compared mat
+            if (otherParam == null) {
                 return false;
             }
 
-            if (param instanceof MatParamTexture) {
-                MatParamTexture tex = (MatParamTexture) param;
-                MatParamTexture texOrig = (MatParamTexture) paramOrig;
-                //comparing textures
-                if (getTextureId(tex) != getTextureId(texOrig)) {
-                    return false;
-                }
-            } else {
-                if (!param.getValue().equals(paramOrig.getValue())) {
-                    return false;
-                }
+            if (!otherParam.equals(thisParam)) {
+                return false;
             }
         }
 
-
-        //comparing additional render states
+        // Comparing additional render states
         if (additionalState == null) {
-            if (mat.additionalState != null) {
+            if (other.additionalState != null) {
                 return false;
             }
         } else {
-            if (!additionalState.equals(mat.additionalState)) {
+            if (!additionalState.equals(other.additionalState)) {
                 return false;
             }
         }
-
-
+        
         return true;
     }
 
-    private int getTextureId(MatParamTexture param) {
-        if (param.getTextureValue() != null && param.getTextureValue().getImage() != null) {
-            return param.getTextureValue().getImage().getId();
-        }
-        return -1;
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 29 * hash + (this.def != null ? this.def.hashCode() : 0);
+        hash = 29 * hash + (this.paramValues != null ? this.paramValues.hashCode() : 0);
+        hash = 29 * hash + (this.technique != null ? this.technique.getDef().getName().hashCode() : 0);
+        hash = 29 * hash + (this.additionalState != null ? this.additionalState.hashCode() : 0);
+        return hash;
     }
 
     /**
@@ -701,18 +688,17 @@ public class Material implements Asset, Cloneable, Savable, Comparable<Material>
     }
 
     /**
-     * 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>
+     * 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.

+ 67 - 1
engine/src/core/com/jme3/material/RenderState.java

@@ -304,6 +304,7 @@ public class RenderState implements Cloneable, Savable {
     StencilOperation backStencilDepthPassOperation = StencilOperation.Keep;
     TestFunction frontStencilFunction = TestFunction.Always;
     TestFunction backStencilFunction = TestFunction.Always;
+    int cachedHashCode = -1;
 
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
@@ -505,6 +506,7 @@ public class RenderState implements Cloneable, Savable {
     public void setPointSprite(boolean pointSprite) {
         applyPointSprite = true;
         this.pointSprite = pointSprite;
+        cachedHashCode = -1;
     }
 
     /**
@@ -522,6 +524,7 @@ public class RenderState implements Cloneable, Savable {
     public void setAlphaFallOff(float alphaFallOff) {
         applyAlphaFallOff = true;
         this.alphaFallOff = alphaFallOff;
+        cachedHashCode = -1;
     }
 
     /**
@@ -539,6 +542,7 @@ public class RenderState implements Cloneable, Savable {
     public void setAlphaTest(boolean alphaTest) {
         applyAlphaTest = true;
         this.alphaTest = alphaTest;
+        cachedHashCode = -1;
     }
 
     /**
@@ -553,6 +557,7 @@ public class RenderState implements Cloneable, Savable {
     public void setColorWrite(boolean colorWrite) {
         applyColorWrite = true;
         this.colorWrite = colorWrite;
+        cachedHashCode = -1;
     }
 
     /**
@@ -570,6 +575,7 @@ public class RenderState implements Cloneable, Savable {
     public void setFaceCullMode(FaceCullMode cullMode) {
         applyCullMode = true;
         this.cullMode = cullMode;
+        cachedHashCode = -1;
     }
 
     /**
@@ -589,6 +595,7 @@ public class RenderState implements Cloneable, Savable {
     public void setBlendMode(BlendMode blendMode) {
         applyBlendMode = true;
         this.blendMode = blendMode;
+        cachedHashCode = -1;
     }
 
     /**
@@ -604,6 +611,7 @@ public class RenderState implements Cloneable, Savable {
     public void setDepthTest(boolean depthTest) {
         applyDepthTest = true;
         this.depthTest = depthTest;
+        cachedHashCode = -1;
     }
 
     /**
@@ -618,6 +626,7 @@ public class RenderState implements Cloneable, Savable {
     public void setDepthWrite(boolean depthWrite) {
         applyDepthWrite = true;
         this.depthWrite = depthWrite;
+        cachedHashCode = -1;
     }
 
     /**
@@ -632,6 +641,7 @@ public class RenderState implements Cloneable, Savable {
     public void setWireframe(boolean wireframe) {
         applyWireFrame = true;
         this.wireframe = wireframe;
+        cachedHashCode = -1;
     }
 
     /**
@@ -651,6 +661,7 @@ public class RenderState implements Cloneable, Savable {
         offsetEnabled = true;
         offsetFactor = factor;
         offsetUnits = units;
+        cachedHashCode = -1;
     }
 
     /**
@@ -699,6 +710,7 @@ public class RenderState implements Cloneable, Savable {
         this.backStencilDepthPassOperation = _backStencilDepthPassOperation;
         this.frontStencilFunction = _frontStencilFunction;
         this.backStencilFunction = _backStencilFunction;
+        cachedHashCode = -1;
     }
 
     /**
@@ -1032,6 +1044,36 @@ public class RenderState implements Cloneable, Savable {
         return applyWireFrame;
     }
 
+    @Override
+    public int hashCode() {
+        if (cachedHashCode == -1){
+            int hash = 7;
+            hash = 79 * hash + (this.pointSprite ? 1 : 0);
+            hash = 79 * hash + (this.wireframe ? 1 : 0);
+            hash = 79 * hash + (this.cullMode != null ? this.cullMode.hashCode() : 0);
+            hash = 79 * hash + (this.depthWrite ? 1 : 0);
+            hash = 79 * hash + (this.depthTest ? 1 : 0);
+            hash = 79 * hash + (this.colorWrite ? 1 : 0);
+            hash = 79 * hash + (this.blendMode != null ? this.blendMode.hashCode() : 0);
+            hash = 79 * hash + (this.alphaTest ? 1 : 0);
+            hash = 79 * hash + Float.floatToIntBits(this.alphaFallOff);
+            hash = 79 * hash + Float.floatToIntBits(this.offsetFactor);
+            hash = 79 * hash + Float.floatToIntBits(this.offsetUnits);
+            hash = 79 * hash + (this.offsetEnabled ? 1 : 0);
+            hash = 79 * hash + (this.stencilTest ? 1 : 0);
+            hash = 79 * hash + (this.frontStencilStencilFailOperation != null ? this.frontStencilStencilFailOperation.hashCode() : 0);
+            hash = 79 * hash + (this.frontStencilDepthFailOperation != null ? this.frontStencilDepthFailOperation.hashCode() : 0);
+            hash = 79 * hash + (this.frontStencilDepthPassOperation != null ? this.frontStencilDepthPassOperation.hashCode() : 0);
+            hash = 79 * hash + (this.backStencilStencilFailOperation != null ? this.backStencilStencilFailOperation.hashCode() : 0);
+            hash = 79 * hash + (this.backStencilDepthFailOperation != null ? this.backStencilDepthFailOperation.hashCode() : 0);
+            hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0);
+            hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0);
+            hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0);
+            cachedHashCode = hash;
+        }
+        return cachedHashCode;
+    }
+
     /**
      * Merges <code>this</code> state and <code>additionalState</code> into
      * the parameter <code>state</code> based on a specific criteria.
@@ -1141,11 +1183,35 @@ public class RenderState implements Cloneable, Savable {
             state.frontStencilFunction = frontStencilFunction;
             state.backStencilFunction = backStencilFunction;
         }
+        state.cachedHashCode = -1;
         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]";
+        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]";
     }
 }

+ 38 - 23
engine/src/core/com/jme3/renderer/Caps.java

@@ -203,7 +203,7 @@ public enum Caps {
      * Supports Format.RGB9E5 for FBO color buffers
      */
     SharedExponentColorBuffer,
-
+    
     /**
      * Supports Format.LATC for textures, this includes
      * support for ATI's 3Dc texture compression.
@@ -226,7 +226,12 @@ public enum Caps {
     /**
      * Supports multisampling on the screen
      */
-    Multisample;
+    Multisample,
+    
+    /**
+     * Supports FBO with Depth24Stencil8 image format
+     */
+    PackedDepthStencilBuffer;
 
     /**
      * Returns true if given the renderer capabilities, the texture
@@ -251,6 +256,8 @@ public enum Caps {
 
         Format fmt = img.getFormat();
         switch (fmt){
+            case Depth24Stencil8:
+                return caps.contains(Caps.PackedDepthStencilBuffer);
             case Depth32F:
                 return caps.contains(Caps.FloatDepthBuffer);
             case LATC:
@@ -268,6 +275,29 @@ public enum Caps {
                 return true;
         }
     }
+    
+    private static boolean supportsColorBuffer(Collection<Caps> caps, RenderBuffer colorBuf){
+        Format colorFmt = colorBuf.getFormat();
+        if (colorFmt.isDepthFormat())
+            return false;
+
+        if (colorFmt.isCompressed())
+            return false;
+
+        switch (colorFmt){
+            case RGB111110F:
+                return caps.contains(Caps.PackedFloatColorBuffer);
+            case RGB16F_to_RGB111110F:
+            case RGB16F_to_RGB9E5:
+            case RGB9E5:
+                return false;
+            default:
+                if (colorFmt.isFloatingPont())
+                    return caps.contains(Caps.FloatColorBuffer);
+
+                return true;
+        }
+    }
 
     /**
      * Returns true if given the renderer capabilities, the framebuffer
@@ -285,9 +315,7 @@ public enum Caps {
          && !caps.contains(Caps.FrameBufferMultisample))
             return false;
 
-        RenderBuffer colorBuf = fb.getColorBuffer();
         RenderBuffer depthBuf = fb.getDepthBuffer();
-
         if (depthBuf != null){
             Format depthFmt = depthBuf.getFormat();
             if (!depthFmt.isDepthFormat()){
@@ -296,28 +324,15 @@ public enum Caps {
                 if (depthFmt == Format.Depth32F
                  && !caps.contains(Caps.FloatDepthBuffer))
                     return false;
+                
+                if (depthFmt == Format.Depth24Stencil8
+                 && !caps.contains(Caps.PackedDepthStencilBuffer))
+                    return false;
             }
         }
-        if (colorBuf != null){
-            Format colorFmt = colorBuf.getFormat();
-            if (colorFmt.isDepthFormat())
-                return false;
-
-            if (colorFmt.isCompressed())
+        for (int i = 0; i < fb.getNumColorBuffers(); i++){
+            if (!supportsColorBuffer(caps, fb.getColorBuffer(i))){
                 return false;
-
-            switch (colorFmt){
-                case RGB111110F:
-                    return caps.contains(Caps.PackedFloatColorBuffer);
-                case RGB16F_to_RGB111110F:
-                case RGB16F_to_RGB9E5:
-                case RGB9E5:
-                    return false;
-                default:
-                    if (colorFmt.isFloatingPont())
-                        return caps.contains(Caps.FloatColorBuffer);
-
-                    return true;
             }
         }
         return true;

+ 3 - 4
engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java

@@ -74,9 +74,8 @@ public class OpaqueComparator implements GeometryComparator {
         Material m1 = o1.getMaterial();
         Material m2 = o2.getMaterial();
 
-        int sortId = m1.compareTo(m2);
-
-        if (sortId == 0){
+        int compareResult = m2.getSortId() - m1.getSortId();
+        if (compareResult == 0){
             // use the same shader.
             // sort front-to-back then.
             float d1 = distanceToCam(o1);
@@ -89,7 +88,7 @@ public class OpaqueComparator implements GeometryComparator {
             else
                 return 1;
         }else{
-            return sortId;
+            return compareResult;
         }
     }
 

+ 1 - 1
engine/src/core/com/jme3/scene/BatchNode.java

@@ -243,7 +243,7 @@ public class BatchNode extends Node implements Savable {
                     if (list == null) {
                         //trying to compare materials with the isEqual method 
                         for ( Map.Entry<Material, List<Geometry>> mat : map.entrySet()) {
-                            if (g.getMaterial().isEqual(mat.getKey())) {
+                            if (g.getMaterial().equals(mat.getKey())) {
                                 list = mat.getValue();
                             }
                         }

+ 8 - 1
engine/src/core/com/jme3/texture/Image.java

@@ -33,6 +33,7 @@
 package com.jme3.texture;
 
 import com.jme3.export.*;
+import com.jme3.renderer.Caps;
 import com.jme3.renderer.Renderer;
 import com.jme3.util.NativeObject;
 import java.io.IOException;
@@ -255,7 +256,13 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
          * Luminance/grayscale texture compression. 
          * Called BC4 in DirectX10.
          */
-        LTC(4, false, true, false);
+        LTC(4, false, true, false),
+        
+        /**
+         * 24-bit depth with 8-bit stencil. 
+         * Check the cap {@link Caps#PackedDepthStencilBuffer}.
+         */
+        Depth24Stencil8(32, true, false, false);
 
         private int bpp;
         private boolean isDepth;

+ 18 - 15
engine/src/core/com/jme3/texture/Texture.java

@@ -32,9 +32,9 @@
 
 package com.jme3.texture;
 
-import com.jme3.asset.Asset;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.CloneableSmartAsset;
 import com.jme3.asset.TextureKey;
 import com.jme3.export.*;
 import com.jme3.util.PlaceholderAssets;
@@ -57,7 +57,7 @@ import java.util.logging.Logger;
  * @author Joshua Slack
  * @version $Id: Texture.java 4131 2009-03-19 20:15:28Z blaine.dev $
  */
-public abstract class Texture implements Asset, Savable, Cloneable {
+public abstract class Texture implements CloneableSmartAsset, Savable, Cloneable {
 
     public enum Type {
 
@@ -502,7 +502,11 @@ public abstract class Texture implements Asset, Savable, Cloneable {
             return false;
         }
         final Texture other = (Texture) obj;
-        if (this.image != other.image && (this.image == null || !this.image.equals(other.image))) {
+        
+        // NOTE: Since images are generally considered unique assets in jME3,
+        // using the image's equals() implementation is not neccessary here
+        // (would be too slow)
+        if (this.image != other.image) {
             return false;
         }
         if (this.minificationFilter != other.minificationFilter) {
@@ -523,7 +527,10 @@ public abstract class Texture implements Asset, Savable, Cloneable {
     @Override
     public int hashCode() {
         int hash = 5;
-        hash = 67 * hash + (this.image != null ? this.image.hashCode() : 0);
+        // NOTE: Since images are generally considered unique assets in jME3,
+        // using the image's hashCode() implementation is not neccessary here
+        // (would be too slow)
+        hash = 67 * hash + (this.image != null ? System.identityHashCode(this.image) : 0);
         hash = 67 * hash + (this.minificationFilter != null ? this.minificationFilter.hashCode() : 0);
         hash = 67 * hash + (this.magnificationFilter != null ? this.magnificationFilter.hashCode() : 0);
         hash = 67 * hash + (this.shadowCompareMode != null ? this.shadowCompareMode.hashCode() : 0);
@@ -531,33 +538,29 @@ public abstract class Texture implements Asset, Savable, Cloneable {
         return hash;
     }
 
-    
-
-//    public abstract Texture createSimpleClone();
-
-
    /** Retrieve a basic clone of this Texture (ie, clone everything but the
      * image data, which is shared)
      *
      * @return Texture
+     * 
+     * @deprecated Use {@link Texture#clone()} instead.
      */
+    @Deprecated
     public Texture createSimpleClone(Texture rVal) {
         rVal.setMinFilter(minificationFilter);
         rVal.setMagFilter(magnificationFilter);
         rVal.setShadowCompareMode(shadowCompareMode);
-//        rVal.setHasBorder(hasBorder);
         rVal.setAnisotropicFilter(anisotropicFilter);
         rVal.setImage(image); // NOT CLONED.
-//        rVal.memReq = memReq;
         rVal.setKey(key);
         rVal.setName(name);
-//        rVal.setBlendColor(blendColor != null ? blendColor.clone() : null);
-//        if (getTextureKey() != null) {
-//            rVal.setTextureKey(getTextureKey());
-//        }
         return rVal;
     }
 
+    /**
+     * @deprecated Use {@link Texture#clone()} instead.
+     */
+    @Deprecated
     public abstract Texture createSimpleClone();
 
     public void write(JmeExporter e) throws IOException {

+ 36 - 5
engine/src/core/com/jme3/util/ListMap.java

@@ -33,11 +33,8 @@
 package com.jme3.util;
 
 import java.io.Serializable;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Set;
+import java.util.*;
 
 /**
  * Implementation of a Map that favors iteration speed rather than
@@ -45,7 +42,7 @@ import java.util.Set;
  *
  * @author Kirill Vainer
  */
-public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
+public final class ListMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable {
 
     public static void main(String[] args){
         Map<String, String> map = new ListMap<String, String>();
@@ -89,6 +86,29 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
             return new ListMapEntry<K, V>(key, value);
         }
 
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final ListMapEntry<K, V> other = (ListMapEntry<K, V>) obj;
+            if (this.key != other.key && (this.key == null || !this.key.equals(other.key))) {
+                return false;
+            }
+            if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return (this.key != null ? this.key.hashCode() : 0) ^
+                   (this.value != null ? this.value.hashCode() : 0);
+        }
     }
     
     private final HashMap<K, V> backingMap;
@@ -115,6 +135,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
         putAll(map);
     }
 
+    @Override
     public int size() {
 //        return entries.size();
         return backingMap.size();
@@ -135,6 +156,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
         return entries[index].key;
     }
 
+    @Override
     public boolean isEmpty() {
         return size() == 0;
     }
@@ -147,6 +169,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
 //        return a == null ? (b == null) : a.equals(b);
 //    }
 
+    @Override
     public boolean containsKey(Object key) {
         return backingMap.containsKey( (K) key); 
 //        if (key == null)
@@ -160,6 +183,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
 //        return false;
     }
 
+    @Override
     public boolean containsValue(Object value) {
         return backingMap.containsValue( (V) value); 
 //        for (int i = 0; i < entries.size(); i++){
@@ -169,6 +193,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
 //        return false;
     }
 
+    @Override
     public V get(Object key) {
         return backingMap.get( (K) key); 
 //        if (key == null)
@@ -182,6 +207,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
 //        return null;
     }
 
+    @Override
     public V put(K key, V value) {
         if (backingMap.containsKey(key)){
             // set the value on the entry
@@ -222,6 +248,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
 //        return null;
     }
 
+    @Override
     public V remove(Object key) {
         V element = backingMap.remove( (K) key);
         if (element != null){
@@ -255,6 +282,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
 //        return null;
     }
 
+    @Override
     public void putAll(Map<? extends K, ? extends V> map) {
         for (Entry<? extends K, ? extends V> entry : map.entrySet()){
             put(entry.getKey(), entry.getValue());
@@ -275,6 +303,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
 //        }
     }
 
+    @Override
     public void clear() {
         backingMap.clear();
 //        entries.clear();
@@ -287,6 +316,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
         return clone;
     }
 
+    @Override
     public Set<K> keySet() {
         return backingMap.keySet();
 //        HashSet<K> keys = new HashSet<K>();
@@ -297,6 +327,7 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
 //        return keys;
     }
 
+    @Override
     public Collection<V> values() {
         return backingMap.values();
 //        ArrayList<V> values = new ArrayList<V>();