Home | History | Annotate | Download | only in texture
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.texture;
     34 
     35 import com.jme3.asset.Asset;
     36 import com.jme3.asset.AssetKey;
     37 import com.jme3.asset.AssetNotFoundException;
     38 import com.jme3.asset.TextureKey;
     39 import com.jme3.export.*;
     40 import com.jme3.util.PlaceholderAssets;
     41 import java.io.IOException;
     42 import java.util.logging.Level;
     43 import java.util.logging.Logger;
     44 
     45 /**
     46  * <code>Texture</code> defines a texture object to be used to display an
     47  * image on a piece of geometry. The image to be displayed is defined by the
     48  * <code>Image</code> class. All attributes required for texture mapping are
     49  * contained within this class. This includes mipmapping if desired,
     50  * magnificationFilter options, apply options and correction options. Default
     51  * values are as follows: minificationFilter - NearestNeighborNoMipMaps,
     52  * magnificationFilter - NearestNeighbor, wrap - EdgeClamp on S,T and R, apply -
     53  * Modulate, environment - None.
     54  *
     55  * @see com.jme3.texture.Image
     56  * @author Mark Powell
     57  * @author Joshua Slack
     58  * @version $Id: Texture.java 4131 2009-03-19 20:15:28Z blaine.dev $
     59  */
     60 public abstract class Texture implements Asset, Savable, Cloneable {
     61 
     62     public enum Type {
     63 
     64         /**
     65          * Two dimensional texture (default). A rectangle.
     66          */
     67         TwoDimensional,
     68 
     69         /**
     70          * An array of two dimensional textures.
     71          */
     72         TwoDimensionalArray,
     73 
     74         /**
     75          * Three dimensional texture. (A cube)
     76          */
     77         ThreeDimensional,
     78 
     79         /**
     80          * A set of 6 TwoDimensional textures arranged as faces of a cube facing
     81          * inwards.
     82          */
     83         CubeMap;
     84     }
     85 
     86     public enum MinFilter {
     87 
     88         /**
     89          * Nearest neighbor interpolation is the fastest and crudest filtering
     90          * method - it simply uses the color of the texel closest to the pixel
     91          * center for the pixel color. While fast, this results in aliasing and
     92          * shimmering during minification. (GL equivalent: GL_NEAREST)
     93          */
     94         NearestNoMipMaps(false),
     95 
     96         /**
     97          * In this method the four nearest texels to the pixel center are
     98          * sampled (at texture level 0), and their colors are combined by
     99          * weighted averages. Though smoother, without mipmaps it suffers the
    100          * same aliasing and shimmering problems as nearest
    101          * NearestNeighborNoMipMaps. (GL equivalent: GL_LINEAR)
    102          */
    103         BilinearNoMipMaps(false),
    104 
    105         /**
    106          * Same as NearestNeighborNoMipMaps except that instead of using samples
    107          * from texture level 0, the closest mipmap level is chosen based on
    108          * distance. This reduces the aliasing and shimmering significantly, but
    109          * does not help with blockiness. (GL equivalent:
    110          * GL_NEAREST_MIPMAP_NEAREST)
    111          */
    112         NearestNearestMipMap(true),
    113 
    114         /**
    115          * Same as BilinearNoMipMaps except that instead of using samples from
    116          * texture level 0, the closest mipmap level is chosen based on
    117          * distance. By using mipmapping we avoid the aliasing and shimmering
    118          * problems of BilinearNoMipMaps. (GL equivalent:
    119          * GL_LINEAR_MIPMAP_NEAREST)
    120          */
    121         BilinearNearestMipMap(true),
    122 
    123         /**
    124          * Similar to NearestNeighborNoMipMaps except that instead of using
    125          * samples from texture level 0, a sample is chosen from each of the
    126          * closest (by distance) two mipmap levels. A weighted average of these
    127          * two samples is returned. (GL equivalent: GL_NEAREST_MIPMAP_LINEAR)
    128          */
    129         NearestLinearMipMap(true),
    130 
    131         /**
    132          * Trilinear filtering is a remedy to a common artifact seen in
    133          * mipmapped bilinearly filtered images: an abrupt and very noticeable
    134          * change in quality at boundaries where the renderer switches from one
    135          * mipmap level to the next. Trilinear filtering solves this by doing a
    136          * texture lookup and bilinear filtering on the two closest mipmap
    137          * levels (one higher and one lower quality), and then linearly
    138          * interpolating the results. This results in a smooth degradation of
    139          * texture quality as distance from the viewer increases, rather than a
    140          * series of sudden drops. Of course, closer than Level 0 there is only
    141          * one mipmap level available, and the algorithm reverts to bilinear
    142          * filtering (GL equivalent: GL_LINEAR_MIPMAP_LINEAR)
    143          */
    144         Trilinear(true);
    145 
    146         private boolean usesMipMapLevels;
    147 
    148         private MinFilter(boolean usesMipMapLevels) {
    149             this.usesMipMapLevels = usesMipMapLevels;
    150         }
    151 
    152         public boolean usesMipMapLevels() {
    153             return usesMipMapLevels;
    154         }
    155     }
    156 
    157     public enum MagFilter {
    158 
    159         /**
    160          * Nearest neighbor interpolation is the fastest and crudest filtering
    161          * mode - it simply uses the color of the texel closest to the pixel
    162          * center for the pixel color. While fast, this results in texture
    163          * 'blockiness' during magnification. (GL equivalent: GL_NEAREST)
    164          */
    165         Nearest,
    166 
    167         /**
    168          * In this mode the four nearest texels to the pixel center are sampled
    169          * (at the closest mipmap level), and their colors are combined by
    170          * weighted average according to distance. This removes the 'blockiness'
    171          * seen during magnification, as there is now a smooth gradient of color
    172          * change from one texel to the next, instead of an abrupt jump as the
    173          * pixel center crosses the texel boundary. (GL equivalent: GL_LINEAR)
    174          */
    175         Bilinear;
    176 
    177     }
    178 
    179     public enum WrapMode {
    180         /**
    181          * Only the fractional portion of the coordinate is considered.
    182          */
    183         Repeat,
    184         /**
    185          * Only the fractional portion of the coordinate is considered, but if
    186          * the integer portion is odd, we'll use 1 - the fractional portion.
    187          * (Introduced around OpenGL1.4) Falls back on Repeat if not supported.
    188          */
    189         MirroredRepeat,
    190         /**
    191          * coordinate will be clamped to [0,1]
    192          */
    193         Clamp,
    194         /**
    195          * mirrors and clamps the texture coordinate, where mirroring and
    196          * clamping a value f computes:
    197          * <code>mirrorClamp(f) = min(1, max(1/(2*N),
    198          * abs(f)))</code> where N
    199          * is the size of the one-, two-, or three-dimensional texture image in
    200          * the direction of wrapping. (Introduced after OpenGL1.4) Falls back on
    201          * Clamp if not supported.
    202          */
    203         MirrorClamp,
    204         /**
    205          * coordinate will be clamped to the range [-1/(2N), 1 + 1/(2N)] where N
    206          * is the size of the texture in the direction of clamping. Falls back
    207          * on Clamp if not supported.
    208          */
    209         BorderClamp,
    210         /**
    211          * Wrap mode MIRROR_CLAMP_TO_BORDER_EXT mirrors and clamps to border the
    212          * texture coordinate, where mirroring and clamping to border a value f
    213          * computes:
    214          * <code>mirrorClampToBorder(f) = min(1+1/(2*N), max(1/(2*N), abs(f)))</code>
    215          * where N is the size of the one-, two-, or three-dimensional texture
    216          * image in the direction of wrapping." (Introduced after OpenGL1.4)
    217          * Falls back on BorderClamp if not supported.
    218          */
    219         MirrorBorderClamp,
    220         /**
    221          * coordinate will be clamped to the range [1/(2N), 1 - 1/(2N)] where N
    222          * is the size of the texture in the direction of clamping. Falls back
    223          * on Clamp if not supported.
    224          */
    225         EdgeClamp,
    226         /**
    227          * mirrors and clamps to edge the texture coordinate, where mirroring
    228          * and clamping to edge a value f computes:
    229          * <code>mirrorClampToEdge(f) = min(1-1/(2*N), max(1/(2*N), abs(f)))</code>
    230          * where N is the size of the one-, two-, or three-dimensional texture
    231          * image in the direction of wrapping. (Introduced after OpenGL1.4)
    232          * Falls back on EdgeClamp if not supported.
    233          */
    234         MirrorEdgeClamp;
    235     }
    236 
    237     public enum WrapAxis {
    238         /**
    239          * S wrapping (u or "horizontal" wrap)
    240          */
    241         S,
    242         /**
    243          * T wrapping (v or "vertical" wrap)
    244          */
    245         T,
    246         /**
    247          * R wrapping (w or "depth" wrap)
    248          */
    249         R;
    250     }
    251 
    252     /**
    253      * If this texture is a depth texture (the format is Depth*) then
    254      * this value may be used to compare the texture depth to the R texture
    255      * coordinate.
    256      */
    257     public enum ShadowCompareMode {
    258         /**
    259          * Shadow comparison mode is disabled.
    260          * Texturing is done normally.
    261          */
    262         Off,
    263 
    264         /**
    265          * Compares the 3rd texture coordinate R to the value
    266          * in this depth texture. If R <= texture value then result is 1.0,
    267          * otherwise, result is 0.0. If filtering is set to bilinear or trilinear
    268          * the implementation may sample the texture multiple times to provide
    269          * smoother results in the range [0, 1].
    270          */
    271         LessOrEqual,
    272 
    273         /**
    274          * Compares the 3rd texture coordinate R to the value
    275          * in this depth texture. If R >= texture value then result is 1.0,
    276          * otherwise, result is 0.0. If filtering is set to bilinear or trilinear
    277          * the implementation may sample the texture multiple times to provide
    278          * smoother results in the range [0, 1].
    279          */
    280         GreaterOrEqual
    281     }
    282 
    283     /**
    284      * The name of the texture (if loaded as a resource).
    285      */
    286     private String name = null;
    287 
    288     /**
    289      * The image stored in the texture
    290      */
    291     private Image image = null;
    292 
    293     /**
    294      * The texture key allows to reload a texture from a file
    295      * if needed.
    296      */
    297     private TextureKey key = null;
    298 
    299     private MinFilter minificationFilter = MinFilter.BilinearNoMipMaps;
    300     private MagFilter magnificationFilter = MagFilter.Bilinear;
    301     private ShadowCompareMode shadowCompareMode = ShadowCompareMode.Off;
    302     private int anisotropicFilter;
    303 
    304     /**
    305      * @return
    306      */
    307     @Override
    308     public Texture clone(){
    309         try {
    310             return (Texture) super.clone();
    311         } catch (CloneNotSupportedException ex) {
    312             throw new AssertionError();
    313         }
    314     }
    315 
    316     /**
    317      * Constructor instantiates a new <code>Texture</code> object with default
    318      * attributes.
    319      */
    320     public Texture() {
    321     }
    322 
    323     /**
    324      * @return the MinificationFilterMode of this texture.
    325      */
    326     public MinFilter getMinFilter() {
    327         return minificationFilter;
    328     }
    329 
    330     /**
    331      * @param minificationFilter
    332      *            the new MinificationFilterMode for this texture.
    333      * @throws IllegalArgumentException
    334      *             if minificationFilter is null
    335      */
    336     public void setMinFilter(MinFilter minificationFilter) {
    337         if (minificationFilter == null) {
    338             throw new IllegalArgumentException(
    339                     "minificationFilter can not be null.");
    340         }
    341         this.minificationFilter = minificationFilter;
    342     }
    343 
    344     /**
    345      * @return the MagnificationFilterMode of this texture.
    346      */
    347     public MagFilter getMagFilter() {
    348         return magnificationFilter;
    349     }
    350 
    351     /**
    352      * @param magnificationFilter
    353      *            the new MagnificationFilter for this texture.
    354      * @throws IllegalArgumentException
    355      *             if magnificationFilter is null
    356      */
    357     public void setMagFilter(MagFilter magnificationFilter) {
    358         if (magnificationFilter == null) {
    359             throw new IllegalArgumentException(
    360                     "magnificationFilter can not be null.");
    361         }
    362         this.magnificationFilter = magnificationFilter;
    363     }
    364 
    365     /**
    366      * @return The ShadowCompareMode of this texture.
    367      * @see ShadowCompareMode
    368      */
    369     public ShadowCompareMode getShadowCompareMode(){
    370         return shadowCompareMode;
    371     }
    372 
    373     /**
    374      * @param compareMode
    375      *            the new ShadowCompareMode for this texture.
    376      * @throws IllegalArgumentException
    377      *             if compareMode is null
    378      * @see ShadowCompareMode
    379      */
    380     public void setShadowCompareMode(ShadowCompareMode compareMode){
    381         if (compareMode == null){
    382             throw new IllegalArgumentException(
    383                     "compareMode can not be null.");
    384         }
    385         this.shadowCompareMode = compareMode;
    386     }
    387 
    388     /**
    389      * <code>setImage</code> sets the image object that defines the texture.
    390      *
    391      * @param image
    392      *            the image that defines the texture.
    393      */
    394     public void setImage(Image image) {
    395         this.image = image;
    396     }
    397 
    398     /**
    399      * @param key The texture key that was used to load this texture
    400      */
    401     public void setKey(AssetKey key){
    402         this.key = (TextureKey) key;
    403     }
    404 
    405     public AssetKey getKey(){
    406         return this.key;
    407     }
    408 
    409     /**
    410      * <code>getImage</code> returns the image data that makes up this
    411      * texture. If no image data has been set, this will return null.
    412      *
    413      * @return the image data that makes up the texture.
    414      */
    415     public Image getImage() {
    416         return image;
    417     }
    418 
    419     /**
    420      * <code>setWrap</code> sets the wrap mode of this texture for a
    421      * particular axis.
    422      *
    423      * @param axis
    424      *            the texture axis to define a wrapmode on.
    425      * @param mode
    426      *            the wrap mode for the given axis of the texture.
    427      * @throws IllegalArgumentException
    428      *             if axis or mode are null or invalid for this type of texture
    429      */
    430     public abstract void setWrap(WrapAxis axis, WrapMode mode);
    431 
    432     /**
    433      * <code>setWrap</code> sets the wrap mode of this texture for all axis.
    434      *
    435      * @param mode
    436      *            the wrap mode for the given axis of the texture.
    437      * @throws IllegalArgumentException
    438      *             if mode is null or invalid for this type of texture
    439      */
    440     public abstract void setWrap(WrapMode mode);
    441 
    442     /**
    443      * <code>getWrap</code> returns the wrap mode for a given coordinate axis
    444      * on this texture.
    445      *
    446      * @param axis
    447      *            the axis to return for
    448      * @return the wrap mode of the texture.
    449      * @throws IllegalArgumentException
    450      *             if axis is null or invalid for this type of texture
    451      */
    452     public abstract WrapMode getWrap(WrapAxis axis);
    453 
    454     public abstract Type getType();
    455 
    456     public String getName() {
    457         return name;
    458     }
    459 
    460     public void setName(String name) {
    461         this.name = name;
    462     }
    463 
    464     /**
    465      * @return the anisotropic filtering level for this texture. Default value
    466      * is 1 (no anisotrophy), 2 means x2, 4 is x4, etc.
    467      */
    468     public int getAnisotropicFilter() {
    469         return anisotropicFilter;
    470     }
    471 
    472     /**
    473      * @param level
    474      *            the anisotropic filtering level for this texture.
    475      */
    476     public void setAnisotropicFilter(int level) {
    477         if (level < 1)
    478             anisotropicFilter = 1;
    479         else
    480             anisotropicFilter = level;
    481     }
    482 
    483     @Override
    484     public String toString(){
    485         StringBuilder sb = new StringBuilder();
    486         sb.append(getClass().getSimpleName());
    487         sb.append("[name=").append(name);
    488         if (image != null)
    489             sb.append(", image=").append(image.toString());
    490 
    491         sb.append("]");
    492 
    493         return sb.toString();
    494     }
    495 
    496     @Override
    497     public boolean equals(Object obj) {
    498         if (obj == null) {
    499             return false;
    500         }
    501         if (getClass() != obj.getClass()) {
    502             return false;
    503         }
    504         final Texture other = (Texture) obj;
    505         if (this.image != other.image && (this.image == null || !this.image.equals(other.image))) {
    506             return false;
    507         }
    508         if (this.minificationFilter != other.minificationFilter) {
    509             return false;
    510         }
    511         if (this.magnificationFilter != other.magnificationFilter) {
    512             return false;
    513         }
    514         if (this.shadowCompareMode != other.shadowCompareMode) {
    515             return false;
    516         }
    517         if (this.anisotropicFilter != other.anisotropicFilter) {
    518             return false;
    519         }
    520         return true;
    521     }
    522 
    523     @Override
    524     public int hashCode() {
    525         int hash = 5;
    526         hash = 67 * hash + (this.image != null ? this.image.hashCode() : 0);
    527         hash = 67 * hash + (this.minificationFilter != null ? this.minificationFilter.hashCode() : 0);
    528         hash = 67 * hash + (this.magnificationFilter != null ? this.magnificationFilter.hashCode() : 0);
    529         hash = 67 * hash + (this.shadowCompareMode != null ? this.shadowCompareMode.hashCode() : 0);
    530         hash = 67 * hash + this.anisotropicFilter;
    531         return hash;
    532     }
    533 
    534 
    535 
    536 //    public abstract Texture createSimpleClone();
    537 
    538 
    539    /** Retrieve a basic clone of this Texture (ie, clone everything but the
    540      * image data, which is shared)
    541      *
    542      * @return Texture
    543      */
    544     public Texture createSimpleClone(Texture rVal) {
    545         rVal.setMinFilter(minificationFilter);
    546         rVal.setMagFilter(magnificationFilter);
    547         rVal.setShadowCompareMode(shadowCompareMode);
    548 //        rVal.setHasBorder(hasBorder);
    549         rVal.setAnisotropicFilter(anisotropicFilter);
    550         rVal.setImage(image); // NOT CLONED.
    551 //        rVal.memReq = memReq;
    552         rVal.setKey(key);
    553         rVal.setName(name);
    554 //        rVal.setBlendColor(blendColor != null ? blendColor.clone() : null);
    555 //        if (getTextureKey() != null) {
    556 //            rVal.setTextureKey(getTextureKey());
    557 //        }
    558         return rVal;
    559     }
    560 
    561     public abstract Texture createSimpleClone();
    562 
    563     public void write(JmeExporter e) throws IOException {
    564         OutputCapsule capsule = e.getCapsule(this);
    565         capsule.write(name, "name", null);
    566 
    567         if (key == null){
    568             // no texture key is set, try to save image instead then
    569             capsule.write(image, "image", null);
    570         }else{
    571             capsule.write(key, "key", null);
    572         }
    573 
    574         capsule.write(anisotropicFilter, "anisotropicFilter", 1);
    575         capsule.write(minificationFilter, "minificationFilter",
    576                 MinFilter.BilinearNoMipMaps);
    577         capsule.write(magnificationFilter, "magnificationFilter",
    578                 MagFilter.Bilinear);
    579     }
    580 
    581     public void read(JmeImporter e) throws IOException {
    582         InputCapsule capsule = e.getCapsule(this);
    583         name = capsule.readString("name", null);
    584         key = (TextureKey) capsule.readSavable("key", null);
    585 
    586         // load texture from key, if available
    587         if (key != null) {
    588             // key is available, so try the texture from there.
    589             try {
    590                 Texture loadedTex = e.getAssetManager().loadTexture(key);
    591                 image = loadedTex.getImage();
    592             } catch (AssetNotFoundException ex){
    593                 Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot locate texture {0}", key);
    594                 image = PlaceholderAssets.getPlaceholderImage();
    595             }
    596         }else{
    597             // no key is set on the texture. Attempt to load an embedded image
    598             image = (Image) capsule.readSavable("image", null);
    599             if (image == null){
    600                 // TODO: what to print out here? the texture has no key or data, there's no useful information ..
    601                 // assume texture.name is set even though the key is null
    602                 Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot load embedded image {0}", toString() );
    603             }
    604         }
    605 
    606         anisotropicFilter = capsule.readInt("anisotropicFilter", 1);
    607         minificationFilter = capsule.readEnum("minificationFilter",
    608                 MinFilter.class,
    609                 MinFilter.BilinearNoMipMaps);
    610         magnificationFilter = capsule.readEnum("magnificationFilter",
    611                 MagFilter.class, MagFilter.Bilinear);
    612     }
    613 }