org.jtrfp.trcl.core.Texture.java Source code

Java tutorial

Introduction

Here is the source code for org.jtrfp.trcl.core.Texture.java

Source

/*******************************************************************************
 * This file is part of TERMINAL RECALL
 * Copyright (c) 2012-2014 Chuck Ritola
 * Part of the jTRFP.org project
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     chuck - initial API and implementation
 ******************************************************************************/
package org.jtrfp.trcl.core;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;

import javax.imageio.ImageIO;
import javax.media.opengl.GL3;

import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.jtrfp.trcl.SpecialRAWDimensions;
import org.jtrfp.trcl.TextureBehavior;
import org.jtrfp.trcl.Triangle;
import org.jtrfp.trcl.TriangleList;
import org.jtrfp.trcl.core.VQCodebookManager.RasterRowWriter;
import org.jtrfp.trcl.gpu.GPU;
import org.jtrfp.trcl.img.vq.BufferedImageRGBA8888VL;
import org.jtrfp.trcl.img.vq.ByteBufferVectorList;
import org.jtrfp.trcl.img.vq.ConstantVectorList;
import org.jtrfp.trcl.img.vq.PalettedVectorList;
import org.jtrfp.trcl.img.vq.RGBA8888VectorList;
import org.jtrfp.trcl.img.vq.RasterizedBlockVectorList;
import org.jtrfp.trcl.img.vq.SubtextureVL;
import org.jtrfp.trcl.img.vq.VectorList;
import org.jtrfp.trcl.img.vq.VectorListND;
import org.jtrfp.trcl.img.vq.VectorListRasterizer;
import org.jtrfp.trcl.math.Misc;
import org.jtrfp.trcl.mem.PagedByteBuffer;
import org.jtrfp.trcl.pool.IntArrayList;

public class Texture implements TextureDescription {
    private final TR tr;
    private final GPU gpu;
    private final TextureManager tm;
    private final VQCodebookManager cbm;
    private final TextureTOCWindow toc;
    private final SubTextureWindow stw;
    private Color averageColor;
    private final String debugName;
    private Integer tocIndex;
    private int[] subTextureIDs;
    private int[][] codebookStartOffsetsAbsolute;
    private final boolean uvWrapping;
    private volatile int texturePage;
    private int sideLength;
    private TextureBehavior.Support tbs = new TextureBehavior.Support();

    @Override
    public void finalize() throws Throwable {
        //TOC ID
        if (tocIndex != null)
            toc.free(tocIndex);
        //Subtexture IDs
        if (subTextureIDs != null)
            for (int stID : subTextureIDs)
                stw.free(stID);
        //Codebook entries
        if (codebookStartOffsetsAbsolute != null)
            for (int[] array : codebookStartOffsetsAbsolute) {
                //for(int entry:array){
                //   tm.vqCodebookManager.get().freeCodebook256(entry/256);
                for (int i = 0; i < array.length; i++)
                    array[i] /= 256;//WARNING: This corrupts the original data
                tm.vqCodebookManager.get().freeCodebook256(new IntArrayList(array).setRepresentFullSize(true));
                // }//end for(entries)
            } //end for(arrays)
        super.finalize();
    }//end finalize()

    Texture(Color c, TR tr) {
        this(new PalettedVectorList(colorZeroRasterVL(), colorVL(c)), null,
                "SolidColor r=" + c.getRed() + " g=" + c.getGreen() + " b=" + c.getBlue(), tr, false);
    }//end constructor

    private static VectorList colorZeroRasterVL() {
        return new VectorList() {
            @Override
            public int getNumVectors() {
                return 16;
            }

            @Override
            public int getNumComponentsPerVector() {
                return 1;
            }

            @Override
            public double componentAt(int vectorIndex, int componentIndex) {
                return 0;
            }

            @Override
            public void setComponentAt(int vectorIndex, int componentIndex, double value) {
                throw new RuntimeException("Cannot write to Texture.colorZeroRasterVL VectorList");
            }
        };
    }//end colorZeroRasterVL

    private static VectorList colorVL(Color c) {
        final double[] color = new double[] { c.getRed() / 255., c.getGreen() / 255., c.getBlue() / 255.,
                c.getAlpha() / 255. };

        return new VectorList() {
            @Override
            public int getNumVectors() {
                return 1;
            }

            @Override
            public int getNumComponentsPerVector() {
                return 4;
            }

            @Override
            public double componentAt(int vectorIndex, int componentIndex) {
                return color[componentIndex];
            }

            @Override
            public void setComponentAt(int vectorIndex, int componentIndex, double value) {
                throw new RuntimeException(
                        "Static palette created by Texture(Color c, TR tr) cannot be written to.");
            }
        };
    }//end colorVL(...)

    private Texture(TR tr, String debugName, boolean uvWrapping) {
        this.tr = tr;
        this.gpu = tr.gpu.get();
        this.tm = gpu.textureManager.get();
        this.cbm = tm.vqCodebookManager.get();
        this.toc = tm.getTOCWindow();
        this.stw = tm.getSubTextureWindow();
        this.debugName = debugName.replace('.', '_');
        this.uvWrapping = uvWrapping;
    }//end constructor

    private Texture(Texture parent, double uOff, double vOff, double uSize, double vSize, TR tr,
            boolean uvWrapping) {
        this(tr, "subtexture: " + parent.debugName, uvWrapping);
    }//end constructor

    public Texture subTexture(double uOff, double vOff, double uSize, double vSize) {
        return new Texture(this, uOff, vOff, uSize, vSize, tr, false);
    }

    Texture(PalettedVectorList vlRGBA, PalettedVectorList vlESTuTv, String debugName, TR tr, boolean uvWrapping) {
        this(tr, debugName, uvWrapping);
        vqCompress(vlRGBA, vlESTuTv);
    }//end constructor

    Texture(ByteBuffer imageRGBA8888, ByteBuffer imageESTuTv8888, String debugName, TR tr, boolean uvWrapping) {
        this(tr, debugName, uvWrapping);
        if (imageRGBA8888.capacity() == 0) {
            throw new IllegalArgumentException("Cannot create texture of zero size.");
        } //end if capacity==0
        imageRGBA8888.clear();//Doesn't erase, just resets the tracking vars
        vqCompress(imageRGBA8888, imageESTuTv8888);
    }// end constructor

    private void vqCompress(PalettedVectorList squareImageIndexedRGBA,
            PalettedVectorList squareImageIndexedESTuTv) {
        final double fuzzySideLength = Math.sqrt(squareImageIndexedRGBA.getNumVectors());
        final int sideLength = (int) Math.floor(fuzzySideLength);
        if (!SpecialRAWDimensions.isPowerOfTwo(sideLength))
            System.err.println("WARNING: Calculated dimensions are not power-of-two. Trouble ahead.");
        if (Math.abs(fuzzySideLength - sideLength) > .001)
            System.err.println("WARNING: Calculated dimensions are not perfectly square. Trouble ahead.");
        vqCompress(squareImageIndexedRGBA, squareImageIndexedESTuTv, sideLength);
    }

    private void vqCompress(ByteBuffer imageRGBA8888, ByteBuffer imageESTuTv8888) {
        final double fuzzySideLength = Math.sqrt((imageRGBA8888.capacity() / 4));
        final int sideLength = (int) Math.floor(fuzzySideLength);
        if (!SpecialRAWDimensions.isPowerOfTwo(sideLength))
            System.err.println("WARNING: Calculated dimensions are not power-of-two. Trouble ahead.");
        if (Math.abs(fuzzySideLength - sideLength) > .001)
            System.err.println("WARNING: Calculated dimensions are not perfectly square. Trouble ahead.");
        // Break down into 4x4 blocks
        final ByteBufferVectorList bbvl = new ByteBufferVectorList(imageRGBA8888);
        final RGBA8888VectorList rgba8888vl = new RGBA8888VectorList(bbvl);

        final VectorList bbvlESTuTv = imageESTuTv8888 != null ? new ByteBufferVectorList(imageESTuTv8888)
                : new ConstantVectorList(0, bbvl);
        final RGBA8888VectorList esTuTv8888vl = bbvlESTuTv != null ? new RGBA8888VectorList(bbvlESTuTv) : null;
        vqCompress(rgba8888vl, esTuTv8888vl, sideLength);
    }

    private final void vqCompress(VectorList rgba8888vl, VectorList esTuTv8888vl, final int sideLength) {
        this.sideLength = sideLength;
        final int diameterInCodes = (int) Misc
                .clamp((double) sideLength / (double) VQCodebookManager.CODE_SIDE_LENGTH, 1, Integer.MAX_VALUE);
        final int diameterInSubtextures = (int) Math
                .ceil((double) diameterInCodes / (double) SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER);
        final RasterizedBlockVectorList rbvlRGBA = new RasterizedBlockVectorList(
                new VectorListRasterizer(rgba8888vl, new int[] { sideLength, sideLength }), 4);
        final VectorListND vlrRGBA = rbvlRGBA;
        final RasterizedBlockVectorList rbvlESTuTv = esTuTv8888vl != null
                ? new RasterizedBlockVectorList(
                        new VectorListRasterizer(esTuTv8888vl, new int[] { sideLength, sideLength }), 4)
                : null;
        final VectorListND vlrESTuTv = rbvlESTuTv != null ? rbvlESTuTv : null;
        // Calculate a rough average color by averaging random samples.
        calulateAverageColor(rbvlRGBA);
        // Get a TOC
        tocIndex = toc.create();
        setTexturePage((toc.getPhysicalAddressInBytes(tocIndex) / PagedByteBuffer.PAGE_SIZE_BYTES));
        if (toc.getPhysicalAddressInBytes(tocIndex) % PagedByteBuffer.PAGE_SIZE_BYTES != 0)
            throw new RuntimeException("Physical GPU address not perfectly aligned with page interval.");

        tr.getThreadManager().submitToThreadPool(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                // Create subtextures
                subTextureIDs = new int[diameterInSubtextures * diameterInSubtextures];
                codebookStartOffsetsAbsolute = new int[diameterInSubtextures * diameterInSubtextures][6];
                for (int i = 0; i < subTextureIDs.length; i++) {
                    //Create subtexture ID
                    subTextureIDs[i] = stw.create();
                    tm.vqCodebookManager.get().newCodebook256(new IntArrayList(codebookStartOffsetsAbsolute[i]), 6);
                    for (int off = 0; off < 6; off++) {
                        codebookStartOffsetsAbsolute[i][off] *= 256;
                    }
                } //end for(subTextureIDs)
                tr.getThreadManager().submitToGPUMemAccess(new Callable<Void>() {
                    @Override
                    public final Void call() {
                        //Set magic
                        toc.magic.set(tocIndex, 1337);
                        for (int i = 0; i < subTextureIDs.length; i++) {
                            final int id = subTextureIDs[i];
                            //Convert subtexture index to index of TOC
                            final int tocSubTexIndex = (i % diameterInSubtextures)
                                    + (i / diameterInSubtextures) * TextureTOCWindow.WIDTH_IN_SUBTEXTURES;
                            //Load subtexture ID into TOC
                            toc.subtextureAddrsVec4.setAt(tocIndex, tocSubTexIndex,
                                    stw.getPhysicalAddressInBytes(id) / GPU.BYTES_PER_VEC4);
                            //Render Flags
                            toc.renderFlags.set(tocIndex, (uvWrapping ? 0x1 : 0x0));
                            //Fill the subtexture code start offsets
                            for (int off = 0; off < 6; off++)
                                stw.codeStartOffsetTable.setAt(id, off, codebookStartOffsetsAbsolute[i][off]);
                        } //end for(subTextureIDs)
                        // Set the TOC vars
                        toc.height.set(tocIndex, sideLength);
                        toc.width.set(tocIndex, sideLength);
                        setCodes(diameterInCodes, diameterInSubtextures);
                        return null;
                    }// end run()

                    //REQUIRES GPU MEM ACCESS
                    private final void setCodes(int diameterInCodes, int diameterInSubtextures) {
                        final int numCodes = diameterInCodes * diameterInCodes;
                        for (int i = 0; i < numCodes; i++) {
                            final int codeX = i % diameterInCodes;
                            final int codeY = i / diameterInCodes;
                            setCodeAt(codeX, codeY);
                        } //end for(numCodes)
                    }//end setCodes()
                     //REQUIRES GPU MEM ACCESS

                    private final void setCodeAt(int codeX, int codeY) {
                        final int subtextureX = codeX / SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int subtextureY = codeY / SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int subtextureCodeX = codeX % SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int subtextureCodeY = codeY % SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int codeIdx = subtextureCodeX
                                + subtextureCodeY * SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int subTextureIdx = subtextureX + subtextureY * diameterInSubtextures;
                        final int subtextureID = subTextureIDs[subTextureIdx];
                        new SubtextureVL(stw, subtextureID).setComponentAt(codeIdx, 0, (byte) (codeIdx % 256));//TODO: Could make a lot of garbage.
                    }//end setCodeAt()
                }).get();//end gpuMemThread

                // Push texels to codebook
                for (int codeY = 0; codeY < diameterInCodes; codeY++) {
                    for (int codeX = 0; codeX < diameterInCodes; codeX++) {
                        final int subtextureX = codeX / SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int subtextureY = codeY / SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int subTextureIdx = subtextureX + subtextureY * diameterInSubtextures;
                        final int subtextureCodeX = codeX % SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int subtextureCodeY = codeY % SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int codeIdx = subtextureCodeX
                                + subtextureCodeY * SubTextureWindow.SIDE_LENGTH_CODES_WITH_BORDER;
                        final int globalCodeIndex = codeIdx % 256
                                + codebookStartOffsetsAbsolute[subTextureIdx][codeIdx / 256];
                        setRGBACodebookTexelsAt(vlrRGBA, codeX, codeY, diameterInCodes, globalCodeIndex);
                        if (vlrESTuTv != null)
                            setESTuTvCodebookTexelsAt(vlrESTuTv, codeX, codeY, diameterInCodes, globalCodeIndex);
                    } //end for(codeX)
                } //end for(codeY)
                flushRGBACodeblock256();
                flushESTuTvCodeblock256();
                return null;
            }//end threadPool call()

            private void setRGBACodebookTexelsAt(final VectorListND vlrRGBA, int codeX, int codeY,
                    int diameterInCodes, int globalCodeIndex) {
                final int coord[] = new int[] { codeX, codeY };
                final RasterRowWriter rw = new RowWriterImpl(vlrRGBA, coord);
                try {
                    registerRGBAToBlock256(globalCodeIndex, rw);
                } catch (ArrayIndexOutOfBoundsException e) {
                    throw new RuntimeException("this=" + Texture.this.toString(), e);
                } //end catch(ArrayIndexOutOfBoundsException)
            }// end setCodebookTexelsAt

            private void setESTuTvCodebookTexelsAt(final VectorListND vlrESTuTv, int codeX, int codeY,
                    int diameterInCodes, int globalCodeIndex) {
                final int coord[] = new int[] { codeX, codeY };
                final RasterRowWriter rw = new RowWriterImpl(vlrESTuTv, coord);
                try {
                    registerESTuTvToBlock256(globalCodeIndex, rw);
                } catch (ArrayIndexOutOfBoundsException e) {
                    throw new RuntimeException("this=" + Texture.this.toString(), e);
                } //end catch(ArrayIndexOutOfBoundsException)
            }// end setCodebookTexelsAt

            final class RowWriterImpl implements RasterRowWriter {
                private final VectorListND _vlr;
                private final int[] coord;

                public RowWriterImpl(VectorListND vlrRGBA, int[] coord) {
                    this._vlr = vlrRGBA;
                    this.coord = coord;
                }

                @Override
                public void applyRow(int row, ByteBuffer dest) {
                    int position = row * 16;
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                    dest.put((byte) (_vlr.componentAt(coord, position++) * 255.));
                }// end applyRow
            }//end RasterRowWriter

            private final Map<Integer, RasterRowWriter[]> rgbaBlock256Map = new HashMap<Integer, RasterRowWriter[]>();
            private final Map<Integer, RasterRowWriter[]> ESTuTvBlock256Map = new HashMap<Integer, RasterRowWriter[]>();

            private final void registerRGBAToBlock256(int globalCodeIndex, RasterRowWriter rw) {
                RasterRowWriter[] writers = getRGBABlock256(globalCodeIndex);
                writers[globalCodeIndex % 256] = rw;
            }//end registerRGBAToBlock256

            private final void registerESTuTvToBlock256(int globalCodeIndex, RasterRowWriter rw) {
                RasterRowWriter[] writers = getESTuTvBlock256(globalCodeIndex);
                writers[globalCodeIndex % 256] = rw;
            }//end registerRGBAToBlock256

            private final RasterRowWriter[] getRGBABlock256(int globalCodeIndex) {
                final int key = globalCodeIndex / 256;
                RasterRowWriter[] writers = rgbaBlock256Map.get(key);
                if (writers == null)
                    rgbaBlock256Map.put(key, writers = new RasterRowWriter[256]);
                return writers;
            }//end getRGBABlock256(...)

            private final RasterRowWriter[] getESTuTvBlock256(int globalCodeIndex) {
                final int key = globalCodeIndex / 256;
                RasterRowWriter[] writers = ESTuTvBlock256Map.get(key);
                if (writers == null)
                    ESTuTvBlock256Map.put(key, writers = new RasterRowWriter[256]);
                return writers;
            }//end getESTuTvBlock256(...)

            private final void flushRGBACodeblock256() {
                for (Entry<Integer, RasterRowWriter[]> entry : rgbaBlock256Map.entrySet()) {
                    cbm.setRGBABlock256(entry.getKey(), entry.getValue());
                } //end for(entries)
            }//end flushRGBACodeblock256()

            private final void flushESTuTvCodeblock256() {
                for (Entry<Integer, RasterRowWriter[]> entry : ESTuTvBlock256Map.entrySet()) {
                    cbm.setESTuTvBlock256(entry.getKey(), entry.getValue());
                } //end for(entries)
            }//end flushRGBACodeblock256()
        });// end pool thread
    }//end vqCompress(...)

    private void calulateAverageColor(RasterizedBlockVectorList rbvl) {
        float redA = 0, greenA = 0, blueA = 0;
        final double size = rbvl.getNumVectors();
        final int[] dims = rbvl.getDimensions();
        for (int i = 0; i < 10; i++) {
            redA += rbvl.componentAt(new int[] { (int) (Math.random() * dims[0]), (int) (Math.random() * dims[1]) },
                    0);
            greenA += rbvl
                    .componentAt(new int[] { (int) (Math.random() * dims[0]), (int) (Math.random() * dims[1]) }, 1);
            blueA += rbvl
                    .componentAt(new int[] { (int) (Math.random() * dims[0]), (int) (Math.random() * dims[1]) }, 2);
        }
        averageColor = new Color(redA / 10f, greenA / 10f, blueA / 10f);
    }//end calculateAverageColor(...)

    Texture(BufferedImage imgRGBA, BufferedImage imgESTuTv, String debugName, TR tr, boolean uvWrapping) {
        this(tr, debugName, uvWrapping);
        try {
            vqCompress(new BufferedImageRGBA8888VL(imgRGBA),
                    imgESTuTv != null ? new BufferedImageRGBA8888VL(imgESTuTv) : null, imgRGBA.getWidth());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }//end constructor

    public static ByteBuffer RGBA8FromPNG(InputStream is) {
        try {
            BufferedImage bi = ImageIO.read(is);
            return RGBA8FromPNG(bi, 0, 0, bi.getWidth(), bi.getHeight());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }//end RGBA8FromPNG(...)

    public static ByteBuffer RGBA8FromPNG(BufferedImage image, int startX, int startY, int sizeX, int sizeY) {
        //int color;
        ByteBuffer buf = ByteBuffer.allocateDirect(image.getWidth() * image.getHeight() * 4);
        final int[] row = new int[image.getWidth()];
        for (int y = startY; y < startY + sizeY; y++) {
            image.getRGB(0, y, image.getWidth(), 1, row, 0, image.getWidth());
            for (int color : row) {
                buf.put((byte) ((color & 0x00FF0000) >> 16));
                buf.put((byte) ((color & 0x0000FF00) >> 8));
                buf.put((byte) (color & 0x000000FF));
                buf.put((byte) ((color & 0xFF000000) >> 24));
            } // end for(x)
        } // end for(y)
        buf.clear();// Rewind
        return buf;
    }// end RGB8FromPNG(...)

    public static final Color[] GREYSCALE;
    static {
        GREYSCALE = new Color[256];
        for (int i = 0; i < 256; i++) {
            GREYSCALE[i] = new Color(i, i, i);
        }
    }// end static{}

    public static ByteBuffer fragmentRGBA(ByteBuffer input, int quadDepth, int x, int y) {
        final int originalSideLen = (int) Math.sqrt(input.capacity() / 4);
        final int splitAmount = (int) Math.pow(2, quadDepth);
        final int newSideLen = originalSideLen / splitAmount;
        ByteBuffer result = ByteBuffer.allocateDirect((int) (Math.pow(newSideLen, 2) * 4));
        for (int row = y * newSideLen; row < (y + 1) * newSideLen; row++) {
            input.clear();
            input.limit((x + 1) * newSideLen * 4 + row * originalSideLen * 4);
            input.position(x * newSideLen * 4 + row * originalSideLen * 4);
            result.put(input);
        }
        return result;
    }// end fragmentRGBA(...)

    public static ByteBuffer indexed2RGBA8888(ByteBuffer indexedPixels, Color[] palette) {
        Color color;
        ByteBuffer buf = ByteBuffer.allocateDirect(indexedPixels.capacity() * 4);
        final int cap = indexedPixels.capacity();
        for (int i = 0; i < cap; i++) {
            color = palette[(indexedPixels.get() & 0xFF)];
            buf.put((byte) color.getRed());
            buf.put((byte) color.getGreen());
            buf.put((byte) color.getBlue());
            buf.put((byte) color.getAlpha());
        } // end for(i)
        buf.clear();// Rewind
        return buf;
    }// end indexed2RGBA8888(...)

    public static ByteBuffer[] indexed2RGBA8888(ByteBuffer[] indexedPixels, Color[] palette) {
        final int len = indexedPixels.length;
        ByteBuffer[] result = new ByteBuffer[len];
        for (int i = 0; i < len; i++) {
            result[i] = indexed2RGBA8888(indexedPixels[i], palette);
        }
        return result;
    }// end indexed2RGBA8888(...)

    /**
     * @return the uvWrapping
     */
    public boolean isUvWrapping() {
        return uvWrapping;
    }

    /**
     * @return the texturePage
     */
    public int getTexturePage() {
        return texturePage;
    }

    /**
     * @param texturePage the texturePage to set
     */
    public void setTexturePage(int texturePage) {
        this.texturePage = texturePage;
    }

    @Override
    public Color getAverageColor() {
        return averageColor;
    }

    public static final int createTextureID(GL3 gl) {
        IntBuffer ib = IntBuffer.allocate(1);
        gl.glGenTextures(1, ib);
        ib.clear();
        return ib.get();
    }//end createTextureID

    @Override
    public String toString() {
        return "Texture debugName=" + debugName + " width=" + sideLength;
    }

    public int getSideLength() {
        return sideLength;
    }

    /**
     * @param beh
     * @see org.jtrfp.trcl.TextureBehavior.Support#addBehavior(org.jtrfp.trcl.TextureBehavior)
     */
    public void addBehavior(TextureBehavior beh) {
        tbs.addBehavior(beh);
    }

    /**
     * @param beh
     * @see org.jtrfp.trcl.TextureBehavior.Support#removeBehavior(org.jtrfp.trcl.TextureBehavior)
     */
    public void removeBehavior(TextureBehavior beh) {
        tbs.removeBehavior(beh);
    }

    /**
     * @param triangleList
     * @param gpuTVIndex
     * @param numFrames
     * @param thisTriangle
     * @param pos
     * @param vw
     * @see org.jtrfp.trcl.TextureBehavior.Support#apply(org.jtrfp.trcl.TriangleList, int, int, org.jtrfp.trcl.Triangle, org.apache.commons.math3.geometry.euclidean.threed.Vector3D, org.jtrfp.trcl.core.TriangleVertexWindow)
     */
    public void apply(TriangleList triangleList, int gpuTVIndex, int numFrames, Triangle thisTriangle, Vector3D pos,
            TriangleVertexWindow vw) {
        tbs.apply(triangleList, gpuTVIndex, numFrames, thisTriangle, pos, vw);
    }
}// end Texture