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 }