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
33package com.jme3.texture;
34
35import com.jme3.asset.Asset;
36import com.jme3.asset.AssetKey;
37import com.jme3.asset.AssetNotFoundException;
38import com.jme3.asset.TextureKey;
39import com.jme3.export.*;
40import com.jme3.util.PlaceholderAssets;
41import java.io.IOException;
42import java.util.logging.Level;
43import 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 */
60public 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}