Browse Source

Engine :
- Added Volume texture 3D loading support to the DDSLoader
- Added a test case for texture 3D loading
- fixes a minor log issue in Material.java

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

rem..om 14 years ago
parent
commit
91477b73f5

+ 203 - 53
engine/src/core-plugins/com/jme3/texture/plugins/DDSLoader.java

@@ -29,7 +29,6 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.texture.plugins;
 
 import com.jme3.asset.AssetInfo;
@@ -62,14 +61,11 @@ public class DDSLoader implements AssetLoader {
 
     private static final Logger logger = Logger.getLogger(DDSLoader.class.getName());
     private static final boolean forceRGBA = false;
-
     private static final int DDSD_MANDATORY = 0x1007;
     private static final int DDSD_MANDATORY_DX10 = 0x6;
-
     private static final int DDSD_MIPMAPCOUNT = 0x20000;
     private static final int DDSD_LINEARSIZE = 0x80000;
     private static final int DDSD_DEPTH = 0x800000;
-
     private static final int DDPF_ALPHAPIXELS = 0x1;
     private static final int DDPF_FOURCC = 0x4;
     private static final int DDPF_RGB = 0x40;
@@ -79,31 +75,24 @@ public class DDSLoader implements AssetLoader {
     private static final int DDPF_ALPHA = 0x2;
     // used by NVTextureTools to mark normal images.
     private static final int DDPF_NORMAL = 0x80000000;
-
     private static final int SWIZZLE_xGxR = 0x78477852;
-
     private static final int DDSCAPS_COMPLEX = 0x8;
     private static final int DDSCAPS_TEXTURE = 0x1000;
     private static final int DDSCAPS_MIPMAP = 0x400000;
-
     private static final int DDSCAPS2_CUBEMAP = 0x200;
     private static final int DDSCAPS2_VOLUME = 0x200000;
-
     private static final int PF_DXT1 = 0x31545844;
     private static final int PF_DXT3 = 0x33545844;
     private static final int PF_DXT5 = 0x35545844;
     private static final int PF_ATI1 = 0x31495441;
     private static final int PF_ATI2 = 0x32495441; // 0x41544932;
     private static final int PF_DX10 = 0x30315844; // a DX10 format
-    
     private static final int DX10DIM_BUFFER = 0x1,
-                             DX10DIM_TEXTURE1D = 0x2,
-                             DX10DIM_TEXTURE2D = 0x3,
-                             DX10DIM_TEXTURE3D = 0x4;
-
+            DX10DIM_TEXTURE1D = 0x2,
+            DX10DIM_TEXTURE2D = 0x3,
+            DX10DIM_TEXTURE3D = 0x4;
     private static final int DX10MISC_GENERATE_MIPS = 0x1,
-                             DX10MISC_TEXTURECUBE = 0x4;
-
+            DX10MISC_TEXTURECUBE = 0x4;
     private static final double LOG2 = Math.log(2);
     private int width;
     private int height;
@@ -115,60 +104,62 @@ public class DDSLoader implements AssetLoader {
     private int caps2;
     private boolean directx10;
     private boolean compressed;
+    private boolean texture3D;
     private boolean grayscaleOrAlpha;
     private boolean normal;
     private Format pixelFormat;
     private int bpp;
     private int[] sizes;
-    private int redMask,  greenMask,  blueMask,  alphaMask;
+    private int redMask, greenMask, blueMask, alphaMask;
     private DataInput in;
 
     public DDSLoader() {
     }
 
-    public Object load(AssetInfo info) throws IOException{
-        if (!(info.getKey() instanceof TextureKey))
+    public Object load(AssetInfo info) throws IOException {
+        if (!(info.getKey() instanceof TextureKey)) {
             throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
+        }
 
         InputStream stream = info.openStream();
         in = new LittleEndien(stream);
         loadHeader();
-        ArrayList<ByteBuffer> data = readData( ((TextureKey)info.getKey()).isFlipY() );
-        stream.close();
 
-        return new Image(pixelFormat, width, height, 0, data, sizes);
+        ArrayList<ByteBuffer> data = readData(((TextureKey) info.getKey()).isFlipY());
+        stream.close();
+        return new Image(pixelFormat, width, height, depth, data, sizes);
     }
 
-    public Image load(InputStream stream) throws IOException{
+    public Image load(InputStream stream) throws IOException {
         in = new LittleEndien(stream);
         loadHeader();
         ArrayList<ByteBuffer> data = readData(false);
-        return new Image(pixelFormat, width, height, 0, data, sizes);
+        return new Image(pixelFormat, width, height, depth, data, sizes);
     }
 
-    private void loadDX10Header() throws IOException{
+    private void loadDX10Header() throws IOException {
         int dxgiFormat = in.readInt();
-        if (dxgiFormat != 83){
-            throw new IOException("Only DXGI_FORMAT_BC5_UNORM " +
-                                  "is supported for DirectX10 DDS! Got: "+dxgiFormat);
+        if (dxgiFormat != 83) {
+            throw new IOException("Only DXGI_FORMAT_BC5_UNORM "
+                    + "is supported for DirectX10 DDS! Got: " + dxgiFormat);
         }
         pixelFormat = Format.LATC;
         bpp = 8;
         compressed = true;
 
         int resDim = in.readInt();
-        if (resDim == DX10DIM_TEXTURE3D){
-            // mark texture as 3D
+        if (resDim == DX10DIM_TEXTURE3D) {
+            texture3D = true;
         }
         int miscFlag = in.readInt();
         int arraySize = in.readInt();
-        if (is(miscFlag, DX10MISC_TEXTURECUBE)){
+        if (is(miscFlag, DX10MISC_TEXTURECUBE)) {
             // mark texture as cube
-            if (arraySize != 6){
+            if (arraySize != 6) {
                 throw new IOException("Cubemaps should consist of 6 images!");
             }
         }
-        
+
         in.skipBytes(4); // skip reserved value
     }
 
@@ -185,7 +176,7 @@ public class DDSLoader implements AssetLoader {
         if (!is(flags, DDSD_MANDATORY) && !is(flags, DDSD_MANDATORY_DX10)) {
             throw new IOException("Mandatory flags missing");
         }
-        
+
         height = in.readInt();
         width = in.readInt();
         pitchOrSize = in.readInt();
@@ -199,17 +190,23 @@ public class DDSLoader implements AssetLoader {
         caps2 = in.readInt();
         in.skipBytes(12);
 
-        if (!directx10){
+
+        if (!directx10) {
             if (!is(caps1, DDSCAPS_TEXTURE)) {
                 throw new IOException("File is not a texture");
             }
 
-            if (depth <= 0)
+            if (depth <= 0) {
                 depth = 1;
+            }
 
             if (is(caps2, DDSCAPS2_CUBEMAP)) {
                 depth = 6; // somewhat of a hack, force loading 6 textures if a cubemap
             }
+
+            if (is(caps2, DDSCAPS2_VOLUME)) {
+                texture3D = true;
+            }
         }
 
         int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(height, width)) / LOG2);
@@ -220,14 +217,14 @@ public class DDSLoader implements AssetLoader {
             } else if (mipMapCount != expectedMipmaps) {
                 // changed to warning- images often do not have the required amount,
                 // or specify that they have mipmaps but include only the top level..
-                logger.log(Level.WARNING, "Got {0} mipmaps, expected {1}", 
+                logger.log(Level.WARNING, "Got {0} mipmaps, expected {1}",
                         new Object[]{mipMapCount, expectedMipmaps});
             }
         } else {
             mipMapCount = 1;
         }
 
-        if (directx10){
+        if (directx10) {
             loadDX10Header();
         }
 
@@ -268,7 +265,7 @@ public class DDSLoader implements AssetLoader {
                 case PF_DXT5:
                     bpp = 8;
                     pixelFormat = Image.Format.DXT5;
-                    if (swizzle == SWIZZLE_xGxR){
+                    if (swizzle == SWIZZLE_xGxR) {
                         normal = true;
                     }
                     break;
@@ -297,7 +294,7 @@ public class DDSLoader implements AssetLoader {
                     logger.warning("Must use linear size with fourcc");
                     pitchOrSize = size;
                 } else if (pitchOrSize != size) {
-                    logger.log(Level.WARNING, "Expected size = {0}, real = {1}", 
+                    logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
                             new Object[]{size, pitchOrSize});
                 }
             } else {
@@ -321,7 +318,7 @@ public class DDSLoader implements AssetLoader {
                 } else {
                     pixelFormat = Format.RGB8;
                 }
-            } else if (is(pfFlags, DDPF_GRAYSCALE) && is(pfFlags, DDPF_ALPHAPIXELS)){
+            } else if (is(pfFlags, DDPF_GRAYSCALE) && is(pfFlags, DDPF_ALPHAPIXELS)) {
                 switch (bpp) {
                     case 16:
                         pixelFormat = Format.Luminance8Alpha8;
@@ -368,7 +365,7 @@ public class DDSLoader implements AssetLoader {
                     logger.warning("Linear size said to contain valid value but does not");
                     pitchOrSize = size;
                 } else if (pitchOrSize != size) {
-                    logger.log(Level.WARNING, "Expected size = {0}, real = {1}", 
+                    logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
                             new Object[]{size, pitchOrSize});
                 }
             } else {
@@ -464,9 +461,9 @@ public class DDSLoader implements AssetLoader {
      */
     public ByteBuffer readRGB2D(boolean flip, int totalSize) throws IOException {
         int redCount = count(redMask),
-            blueCount = count(blueMask),
-            greenCount = count(greenMask),
-            alphaCount = count(alphaMask);
+                blueCount = count(blueMask),
+                greenCount = count(greenMask),
+                alphaCount = count(alphaMask);
 
         if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) {
             if (alphaMask == 0xFF000000 && bpp == 32) {
@@ -536,23 +533,20 @@ public class DDSLoader implements AssetLoader {
         int mipWidth = width;
         int mipHeight = height;
 
-        int offset = 0;
         for (int mip = 0; mip < mipMapCount; mip++) {
-            if (flip){
+            if (flip) {
                 byte[] data = new byte[sizes[mip]];
                 in.readFully(data);
                 ByteBuffer wrapped = ByteBuffer.wrap(data);
                 wrapped.rewind();
                 ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat);
                 buffer.put(flipped);
-            }else{
+            } else {
                 byte[] data = new byte[sizes[mip]];
                 in.readFully(data);
                 buffer.put(data);
             }
 
-            offset += sizes[mip];
-
             mipWidth = Math.max(mipWidth / 2, 1);
             mipHeight = Math.max(mipHeight / 2, 1);
         }
@@ -561,6 +555,153 @@ public class DDSLoader implements AssetLoader {
         return buffer;
     }
 
+    /**
+     * Reads a grayscale image with mipmaps from the InputStream
+     * @param flip Flip the loaded image by Y axis
+     * @param totalSize Total size of the image in bytes including the mipmaps
+     * @return A ByteBuffer containing the grayscale image data with mips.
+     * @throws java.io.IOException If an error occured while reading from InputStream
+     */
+    public ByteBuffer readGrayscale3D(boolean flip, int totalSize) throws IOException {
+        ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize * depth);
+
+        if (bpp == 8) {
+            logger.finest("Source image format: R8");
+        }
+
+        assert bpp == pixelFormat.getBitsPerPixel();
+
+
+        for (int i = 0; i < depth; i++) {
+            int mipWidth = width;
+            int mipHeight = height;
+
+            for (int mip = 0; mip < mipMapCount; mip++) {
+                byte[] data = new byte[sizes[mip]];
+                in.readFully(data);
+                if (flip) {
+                    data = flipData(data, mipWidth * bpp / 8, mipHeight);
+                }
+                buffer.put(data);
+
+                mipWidth = Math.max(mipWidth / 2, 1);
+                mipHeight = Math.max(mipHeight / 2, 1);
+            }
+        }
+        buffer.rewind();
+        return buffer;
+    }
+
+    /**
+     * Reads an uncompressed RGB or RGBA image.
+     *
+     * @param flip Flip the image on the Y axis
+     * @param totalSize Size of the image in bytes including mipmaps
+     * @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_
+     * @throws java.io.IOException If an error occured while reading from InputStream
+     */
+    public ByteBuffer readRGB3D(boolean flip, int totalSize) throws IOException {
+        int redCount = count(redMask),
+                blueCount = count(blueMask),
+                greenCount = count(greenMask),
+                alphaCount = count(alphaMask);
+
+        if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) {
+            if (alphaMask == 0xFF000000 && bpp == 32) {
+                logger.finest("Data source format: BGRA8");
+            } else if (bpp == 24) {
+                logger.finest("Data source format: BGR8");
+            }
+        }
+
+        int sourcebytesPP = bpp / 8;
+        int targetBytesPP = pixelFormat.getBitsPerPixel() / 8;
+
+        ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize * depth);
+
+        for (int k = 0; k < depth; k++) {
+            //   ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize);
+            int mipWidth = width;
+            int mipHeight = height;
+            int offset = k * totalSize;
+            byte[] b = new byte[sourcebytesPP];
+            for (int mip = 0; mip < mipMapCount; mip++) {
+                for (int y = 0; y < mipHeight; y++) {
+                    for (int x = 0; x < mipWidth; x++) {
+                        in.readFully(b);
+
+                        int i = byte2int(b);
+
+                        byte red = (byte) (((i & redMask) >> redCount));
+                        byte green = (byte) (((i & greenMask) >> greenCount));
+                        byte blue = (byte) (((i & blueMask) >> blueCount));
+                        byte alpha = (byte) (((i & alphaMask) >> alphaCount));
+
+                        if (flip) {
+                            dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP);
+                        }
+                        //else
+                        //    dataBuffer.position(offset + (y * width + x) * targetBytesPP);
+
+                        if (alphaMask == 0) {
+                            dataBuffer.put(red).put(green).put(blue);
+                        } else {
+                            dataBuffer.put(red).put(green).put(blue).put(alpha);
+                        }
+                    }
+                }
+
+                offset += (mipWidth * mipHeight * targetBytesPP);
+
+                mipWidth = Math.max(mipWidth / 2, 1);
+                mipHeight = Math.max(mipHeight / 2, 1);
+            }
+        }
+        dataBuffer.rewind();
+        return dataBuffer;
+    }
+
+    /**
+     * Reads a DXT compressed image from the InputStream
+     *
+     * @param totalSize Total size of the image in bytes, including mipmaps
+     * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_
+     * @throws java.io.IOException If an error occured while reading from InputStream
+     */
+    public ByteBuffer readDXT3D(boolean flip, int totalSize) throws IOException {
+        logger.finest("Source image format: DXT");
+
+        ByteBuffer bufferAll = BufferUtils.createByteBuffer(totalSize * depth);
+
+        for (int i = 0; i < depth; i++) {
+            ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize);
+            int mipWidth = width;
+            int mipHeight = height;
+            for (int mip = 0; mip < mipMapCount; mip++) {
+                if (flip) {
+                    byte[] data = new byte[sizes[mip]];
+                    in.readFully(data);
+                    ByteBuffer wrapped = ByteBuffer.wrap(data);
+                    wrapped.rewind();
+                    ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat);
+                    flipped.rewind();
+                    buffer.put(flipped);
+                } else {
+                    byte[] data = new byte[sizes[mip]];
+                    in.readFully(data);
+                    buffer.put(data);
+                }
+
+                mipWidth = Math.max(mipWidth / 2, 1);
+                mipHeight = Math.max(mipHeight / 2, 1);
+            }
+            buffer.rewind();
+            bufferAll.put(buffer);
+        }
+
+        return bufferAll;
+    }
+
     /**
      * Reads the image data from the InputStream in the required format.
      * If the file contains a cubemap image, it is loaded as 6 ByteBuffers
@@ -583,19 +724,28 @@ public class DDSLoader implements AssetLoader {
         }
 
         ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>();
-        if (depth > 1){
-            for (int i = 0; i < depth; i++){
+        if (depth > 1 && !texture3D) {
+            for (int i = 0; i < depth; i++) {
                 if (compressed) {
-                    allMaps.add(readDXT2D(flip,totalSize));
+                    allMaps.add(readDXT2D(flip, totalSize));
                 } else if (grayscaleOrAlpha) {
                     allMaps.add(readGrayscale2D(flip, totalSize));
                 } else {
                     allMaps.add(readRGB2D(flip, totalSize));
                 }
             }
+        } else if (texture3D) {
+            if (compressed) {
+                allMaps.add(readDXT3D(flip, totalSize));
+            } else if (grayscaleOrAlpha) {
+                allMaps.add(readGrayscale3D(flip, totalSize));
+            } else {
+                allMaps.add(readRGB3D(flip, totalSize));
+            }
+
         } else {
             if (compressed) {
-                allMaps.add(readDXT2D(flip,totalSize));
+                allMaps.add(readDXT2D(flip, totalSize));
             } else if (grayscaleOrAlpha) {
                 allMaps.add(readGrayscale2D(flip, totalSize));
             } else {

+ 31 - 18
engine/src/core/com/jme3/asset/TextureKey.java

@@ -29,7 +29,6 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.asset;
 
 import com.jme3.export.JmeExporter;
@@ -39,6 +38,7 @@ import com.jme3.export.OutputCapsule;
 import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture2D;
+import com.jme3.texture.Texture3D;
 import com.jme3.texture.TextureCubeMap;
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -48,23 +48,24 @@ public class TextureKey extends AssetKey<Texture> {
     private boolean generateMips;
     private boolean flipY;
     private boolean asCube;
+    private boolean asTexture3D;
     private int anisotropy;
 
-    public TextureKey(String name, boolean flipY){
+    public TextureKey(String name, boolean flipY) {
         super(name);
         this.flipY = flipY;
     }
 
-    public TextureKey(String name){
+    public TextureKey(String name) {
         super(name);
         this.flipY = true;
     }
 
-    public TextureKey(){
+    public TextureKey() {
     }
 
     @Override
-    public String toString(){
+    public String toString() {
         return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmaped)" : "");
     }
 
@@ -73,39 +74,43 @@ public class TextureKey extends AssetKey<Texture> {
      * @return true to enable smart cache
      */
     @Override
-    public boolean useSmartCache(){
+    public boolean useSmartCache() {
         return true;
     }
 
     @Override
-    public Object createClonedInstance(Object asset){
+    public Object createClonedInstance(Object asset) {
         Texture tex = (Texture) asset;
         return tex.createSimpleClone();
     }
 
     @Override
-    public Object postProcess(Object asset){
+    public Object postProcess(Object asset) {
         Image img = (Image) asset;
-        if (img == null)
+        if (img == null) {
             return null;
+        }
 
         Texture tex;
-        if (isAsCube()){
-            if (isFlipY()){
+        if (isAsCube()) {
+            if (isFlipY()) {
                 // also flip -y and +y image in cubemap
                 ByteBuffer pos_y = img.getData(2);
                 img.setData(2, img.getData(3));
                 img.setData(3, pos_y);
             }
             tex = new TextureCubeMap();
-        }else{
+        } else if (isAsTexture3D()) {
+            tex = new Texture3D();
+        } else {
             tex = new Texture2D();
         }
 
         // enable mipmaps if image has them
         // or generate them if requested by user
-        if (img.hasMipmaps() || isGenerateMips())
+        if (img.hasMipmaps() || isGenerateMips()) {
             tex.setMinFilter(Texture.MinFilter.Trilinear);
+        }
 
         tex.setAnisotropicFilter(getAnisotropy());
         tex.setName(getName());
@@ -141,15 +146,23 @@ public class TextureKey extends AssetKey<Texture> {
         this.generateMips = generateMips;
     }
 
+    public boolean isAsTexture3D() {
+        return asTexture3D;
+    }
+
+    public void setAsTexture3D(boolean asTexture3D) {
+        this.asTexture3D = asTexture3D;
+    }
+
     @Override
-    public boolean equals(Object other){
-        if (!(other instanceof TextureKey)){
+    public boolean equals(Object other) {
+        if (!(other instanceof TextureKey)) {
             return false;
         }
-        return super.equals(other) && isFlipY() == ((TextureKey)other).isFlipY();
+        return super.equals(other) && isFlipY() == ((TextureKey) other).isFlipY();
     }
 
-    public void write(JmeExporter ex) throws IOException{
+    public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(flipY, "flip_y", false);
@@ -159,7 +172,7 @@ public class TextureKey extends AssetKey<Texture> {
     }
 
     @Override
-    public void read(JmeImporter im) throws IOException{
+    public void read(JmeImporter im) throws IOException {
         super.read(im);
         InputCapsule ic = im.getCapsule(this);
         flipY = ic.readBoolean("flip_y", false);

+ 3 - 4
engine/src/core/com/jme3/material/Material.java

@@ -368,9 +368,8 @@ public class Material implements Asset, Cloneable, Savable, Comparable<Material>
         }
 
         if (type != null && paramDef.getVarType() != type) {
-            logger.logp(Level.WARNING, "Material parameter being set: {0} with "
-                    + "type {1} doesn't match definition type {2}",
-                    name, type.name(), paramDef.getVarType());
+            logger.log(Level.WARNING, "Material parameter being set: {0} with "
+                    + "type {1} doesn't match definition type {2}", new Object[]{name, type.name(), paramDef.getVarType()} );
         }
 
         return newName;
@@ -497,7 +496,7 @@ public class Material implements Asset, Cloneable, Savable, Comparable<Material>
             return;
         }
 
-        VarType paramType = null;
+        VarType paramType = null;        
         switch (value.getType()) {
             case TwoDimensional:
                 paramType = VarType.Texture2D;

BIN
engine/src/test-data/Textures/3D/flame.dds


+ 65 - 0
engine/src/test/jme3test/texture/TestTexture3DLoading.java

@@ -0,0 +1,65 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3test.texture;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class TestTexture3DLoading extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestTexture3DLoading app = new TestTexture3DLoading();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+        flyCam.setEnabled(false);
+
+
+        Quad q = new Quad(10, 10);
+
+        Geometry geom = new Geometry("Quad", q);
+        Material material = new Material(assetManager, "jme3test/texture/tex3DThumb.j3md");
+        TextureKey key = new TextureKey("Textures/3D/flame.dds");
+        key.setGenerateMips(true);
+        key.setAsTexture3D(true);
+
+        Texture t = assetManager.loadTexture(key);
+
+        int rows = 4;//4 * 4
+
+        q.scaleTextureCoordinates(new Vector2f(rows, rows));
+
+        //The image only have 8 pictures and we have 16 thumbs, the data will be interpolated by the GPU
+        material.setFloat("InvDepth", 1f / 16f);
+        material.setInt("Rows", rows);
+        material.setTexture("Texture", t);
+        geom.setMaterial(material);
+
+        rootNode.attachChild(geom);
+
+        cam.setLocation(new Vector3f(4.7444625f, 5.160054f, 13.1939f));
+    }
+}

+ 14 - 0
engine/src/test/jme3test/texture/tex3DThumb.frag

@@ -0,0 +1,14 @@
+uniform sampler3D m_Texture;
+uniform int m_Rows;
+uniform float m_InvDepth;
+
+varying vec2 texCoord;
+
+void main(){
+float depthx=floor(texCoord.x);
+    float depthy=(m_Rows-1.0) - floor(texCoord.y);    
+    //vec3 texC=vec3(texCoord.x,texCoord.y ,0.7);//
+  
+        vec3 texC=vec3(fract(texCoord.x),fract(texCoord.y),(depthy*m_Rows+depthx)*m_InvDepth);//
+    gl_FragColor= texture3D(m_Texture,texC);
+}

+ 18 - 0
engine/src/test/jme3test/texture/tex3DThumb.j3md

@@ -0,0 +1,18 @@
+MaterialDef Tex3DThumb {
+
+    MaterialParameters {
+        Texture3D Texture
+        Int Rows;
+        Float InvDepth;
+    }
+
+    Technique {
+        VertexShader GLSL100:   jme3test/texture/tex3DThumb.vert
+        FragmentShader GLSL100: jme3test/texture/tex3DThumb.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+    }
+
+}

+ 11 - 0
engine/src/test/jme3test/texture/tex3DThumb.vert

@@ -0,0 +1,11 @@
+uniform mat4 g_WorldViewProjectionMatrix;
+
+attribute vec2 inTexCoord;
+attribute vec3 inPosition;
+
+varying vec2 texCoord;
+
+void main(){
+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);
+    texCoord=inTexCoord;
+}