Package com.ardor3d.extension.terrain.client

Source Code of com.ardor3d.extension.terrain.client.TextureGridCache$TileLoadingData

/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/

package com.ardor3d.extension.terrain.client;

import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ardor3d.extension.terrain.client.functions.CacheFunctionUtil;
import com.ardor3d.extension.terrain.client.functions.SourceCacheFunction;
import com.ardor3d.extension.terrain.util.DoubleBufferedList;
import com.ardor3d.extension.terrain.util.IntColorUtils;
import com.ardor3d.extension.terrain.util.Region;
import com.ardor3d.extension.terrain.util.Tile;
import com.ardor3d.image.TextureStoreFormat;
import com.ardor3d.math.MathUtils;
import com.google.common.collect.Sets;

/**
* Special tile/grid based cache for texture data
*/
public class TextureGridCache implements TextureCache, Runnable {
    /** The Constant logger. */
    private static final Logger logger = Logger.getLogger(TextureGridCache.class.getName());

    private final TextureSource source;

    private final TextureCache parentCache;

    private final int cacheSize;
    private final int tileSize;
    private final int dataSize;

    private final byte[] data;
    private final CacheData[][] cache;
    private final int destinationSize;

    private final int clipmapLevel;
    private final int requestedLevel;

    private final Set<TileLoadingData> currentTiles = Sets.newHashSet();
    private Set<TileLoadingData> newThreadTiles = Sets.newHashSet();
    private Set<TileLoadingData> backThreadTiles = Sets.newHashSet();
    private final Object SWAP_LOCK = new Object();
    private int backCurrentTileX = Integer.MAX_VALUE;
    private int backCurrentTileY = Integer.MAX_VALUE;
    private boolean updated = false;

    private final TextureConfiguration textureConfiguration;
    private SourceCacheFunction function;

    private final int TILELOCATOR_SLEEP = 100;

    private final boolean useAlpha;
    private final int colorBits;

    // Debug
    private final boolean enableDebug = true;
    private final Set<TileLoadingData> debugTiles = Sets.newHashSet();

    public Set<TileLoadingData> getDebugTiles() {
        Set<TileLoadingData> copyTiles = null;
        synchronized (debugTiles) {
            copyTiles = Sets.newHashSet(debugTiles);
        }
        return copyTiles;
    }

    private final ThreadPoolExecutor tileThreadService;

    private DoubleBufferedList<Region> mailBox;

    private Set<Tile> validTiles;

    public TextureGridCache(final TextureCache parentCache, final int cacheSize, final TextureSource source,
            final int tileSize, final int destinationSize, final TextureConfiguration textureConfiguration,
            final int clipmapLevel, final int requestedLevel, final ThreadPoolExecutor tileThreadService) {
        this.parentCache = parentCache;
        this.cacheSize = cacheSize;
        this.source = source;
        this.tileSize = tileSize;
        dataSize = tileSize * cacheSize;
        this.destinationSize = destinationSize;
        this.textureConfiguration = textureConfiguration;
        useAlpha = textureConfiguration.isUseAlpha();
        colorBits = useAlpha ? 4 : 3;
        this.clipmapLevel = clipmapLevel;
        this.requestedLevel = requestedLevel;

        this.tileThreadService = tileThreadService;
        // tileThreadService = new ThreadPoolExecutor(nrThreads, nrThreads, 0L, TimeUnit.MILLISECONDS,
        // new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder()
        // .setThreadFactory(Executors.defaultThreadFactory()).setDaemon(true)
        // .setNameFormat("TextureTileThread-%s").setPriority(Thread.MIN_PRIORITY).build());

        data = new byte[dataSize * dataSize * colorBits];
        for (int i = 0; i < dataSize * dataSize * colorBits; i++) {
            data[i] = (byte) 1;
        }

        cache = new CacheData[cacheSize][cacheSize];
        for (int i = 0; i < cacheSize; i++) {
            for (int j = 0; j < cacheSize; j++) {
                cache[i][j] = new CacheData();
            }
        }
    }

    private boolean started = false;
    private final int locatorSize = 20;
    private Tile locatorTile = new Tile(Integer.MAX_VALUE, Integer.MAX_VALUE);

    private boolean exit = false;

    @Override
    public Set<Tile> handleUpdateRequests() {
        Set<Tile> updateTiles;
        try {
            updateTiles = source.getInvalidTiles(requestedLevel, backCurrentTileX - cacheSize / 2, backCurrentTileY
                    - cacheSize / 2, cacheSize, cacheSize);
            if (updateTiles == null || updateTiles.isEmpty()) {
                return null;
            }
        } catch (final Exception e) {
            logger.log(Level.WARNING, "Exception processing updates", e);
            return null;
        }

        for (final Tile tile : updateTiles) {
            ByteBuffer sourceData;
            try {
                sourceData = source.getTile(requestedLevel, tile);
            } catch (final InterruptedException e) {
                // XXX: Loading can be interrupted
                return null;
            } catch (final Exception e) {
                logger.log(Level.WARNING, "Exception getting tile", e);
                return null;
            }

            if (sourceData == null) {
                continue;
            }

            final int destX = MathUtils.moduloPositive(tile.getX(), cacheSize);
            final int destY = MathUtils.moduloPositive(tile.getY(), cacheSize);

            final TextureStoreFormat format = textureConfiguration.getTextureDataType(source.getContributorId(
                    requestedLevel, tile));
            CacheFunctionUtil.applyFunction(useAlpha, function, sourceData, data, destX, destY, format, tileSize,
                    dataSize);
        }

        return updateTiles;
    }

    public void setCurrentPosition(final int x, final int y) {
        final int tileX = MathUtils.floor((float) x / tileSize);
        final int tileY = MathUtils.floor((float) y / tileSize);

        final int diffX = tileX - backCurrentTileX;
        final int diffY = tileY - backCurrentTileY;

        if (diffX == 0 && diffY == 0) {
            return;
        }

        synchronized (SWAP_LOCK) {
            backCurrentTileX = tileX;
            backCurrentTileY = tileY;

            final Set<TileLoadingData> newTiles = Sets.newHashSet();
            for (int i = 0; i < cacheSize; i++) {
                for (int j = 0; j < cacheSize; j++) {
                    final int sourceX = tileX + j - cacheSize / 2;
                    final int sourceY = tileY + i - cacheSize / 2;

                    final int destX = MathUtils.moduloPositive(sourceX, cacheSize);
                    final int destY = MathUtils.moduloPositive(sourceY, cacheSize);

                    newTiles.add(new TileLoadingData(mailBox, new Tile(sourceX, sourceY), new Tile(destX, destY),
                            source, cache, data, tileSize, dataSize, function, textureConfiguration, useAlpha,
                            clipmapLevel, requestedLevel));
                }
            }

            final Iterator<TileLoadingData> tileIterator = currentTiles.iterator();
            while (tileIterator.hasNext()) {
                final TileLoadingData data = tileIterator.next();

                if (!newTiles.contains(data)) {
                    cache[data.destTile.getX()][data.destTile.getY()].isValid = false;

                    data.isCancelled = true;
                    final Future<Boolean> future = data.future;
                    if (future != null && !future.isDone()) {
                        future.cancel(true);
                    }
                    tileIterator.remove();
                    if (backThreadTiles.contains(data)) {
                        backThreadTiles.remove(data);
                    }
                } else {
                    newTiles.remove(data);
                }
            }

            backThreadTiles.addAll(newTiles);
            updated = true;

            currentTiles.addAll(newTiles);
        }

        tileThreadService.purge();

        if (enableDebug) {
            synchronized (debugTiles) {
                debugTiles.clear();
                debugTiles.addAll(currentTiles);
            }
        }

        if (!started) {
            final Thread textureCacheThread = new Thread(this, "TextureGridCache-" + clipmapLevel);
            textureCacheThread.setDaemon(true);
            textureCacheThread.start();
            started = true;
        }
    }

    @Override
    public void run() {
        while (!exit) {
            try {
                Thread.sleep(TILELOCATOR_SLEEP);
            } catch (final InterruptedException e) {
                e.printStackTrace();
            }

            int tileX;
            int tileY;
            boolean needsUpdate = false;
            synchronized (SWAP_LOCK) {
                final Set<TileLoadingData> tmp = newThreadTiles;
                newThreadTiles = backThreadTiles;
                backThreadTiles = tmp;
                backThreadTiles.clear();

                tileX = backCurrentTileX;
                tileY = backCurrentTileY;

                needsUpdate = updated;
                updated = false;
            }

            if (needsUpdate) {
                if (tileX <= locatorTile.getX() - locatorSize / 2 + 1
                        || tileX >= locatorTile.getX() + locatorSize / 2 - 2
                        || tileY <= locatorTile.getY() - locatorSize / 2 + 1
                        || tileY >= locatorTile.getY() + locatorSize / 2 - 2) {
                    try {
                        validTiles = source.getValidTiles(requestedLevel, tileX - locatorSize / 2, tileY - locatorSize
                                / 2, locatorSize, locatorSize);
                    } catch (final Exception e) {
                        logger.log(Level.WARNING, "Exception getting source info", e);
                    }
                    locatorTile = new Tile(tileX, tileY);
                }

                threadedUpdateTiles();
            }
        }
    }

    private void threadedUpdateTiles() {
        final Iterator<TileLoadingData> tileIterator = newThreadTiles.iterator();
        while (tileIterator.hasNext()) {
            final TileLoadingData data = tileIterator.next();
            if (validTiles == null || validTiles.contains(data.sourceTile)) {
                cache[data.destTile.getX()][data.destTile.getY()].isValid = false;

                final Future<Boolean> future = tileThreadService.submit(data);
                data.future = future;
            }
            tileIterator.remove();
        }
    }

    @Override
    public int getColor(final int x, final int z) {
        int tileX = MathUtils.floor((float) x / tileSize);
        int tileY = MathUtils.floor((float) z / tileSize);
        tileX = MathUtils.moduloPositive(tileX, cacheSize);
        tileY = MathUtils.moduloPositive(tileY, cacheSize);
        final CacheData tileData = cache[tileX][tileY];

        if (!tileData.isValid) {
            if (parentCache != null) {
                if (x % 2 == 0 && z % 2 == 0) {
                    return parentCache.getColor(x / 2, z / 2);
                } else {
                    return parentCache.getSubColor(x / 2f, z / 2f);
                }
            } else {
                return 0;
            }
        } else {
            final int dataX = MathUtils.moduloPositive(x, dataSize);
            final int dataY = MathUtils.moduloPositive(z, dataSize);
            final int sourceIndex = (dataY * dataSize + dataX) * colorBits;

            int color = 0;
            if (useAlpha) {
                color = IntColorUtils.getColor(data[sourceIndex + 0], data[sourceIndex + 1], data[sourceIndex + 2],
                        data[sourceIndex + 3]);
            } else {
                color = IntColorUtils.getColor(data[sourceIndex + 0], data[sourceIndex + 1], data[sourceIndex + 2],
                        (byte) 0);
            }

            // if (rgbStore.r == 0 && rgbStore.g == 0 && rgbStore.b == 0) {
            // if (parentCache != null) {
            // if (x % 2 == 0 && z % 2 == 0) {
            // return parentCache.getRGB(x / 2, z / 2, rgbStore);
            // } else {
            // return parentCache.getSubRGB(x / 2f, z / 2f, rgbStore);
            // }
            // } else {
            // return minRGB;
            // }
            // }

            return color;
        }
    }

    @Override
    public int getSubColor(final float x, final float z) {
        int tileX = MathUtils.floor(x / tileSize);
        int tileY = MathUtils.floor(z / tileSize);
        tileX = MathUtils.moduloPositive(tileX, cacheSize);
        tileY = MathUtils.moduloPositive(tileY, cacheSize);
        final CacheData tileData = cache[tileX][tileY];

        if (!tileData.isValid) {
            if (parentCache != null) {
                return parentCache.getSubColor(x / 2f, z / 2f);
            } else {
                return 0;
            }
        } else {
            final int col = MathUtils.floor(x);
            final int row = MathUtils.floor(z);
            final double intOnX = x - col;
            final double intOnZ = z - row;

            final int topLeft = getColor(col, row);
            final int topRight = getColor(col + 1, row);
            final int top = IntColorUtils.lerp(intOnX, topLeft, topRight);

            final int bottomLeft = getColor(col, row + 1);
            final int bottomRight = getColor(col + 1, row + 1);
            final int bottom = IntColorUtils.lerp(intOnX, bottomLeft, bottomRight);

            return IntColorUtils.lerp(intOnZ, top, bottom);
        }
    }

    @Override
    public void updateRegion(final ByteBuffer destinationData, final int sourceX, final int sourceY, final int destX,
            final int destY, final int width, final int height) {
        final byte[] rgbArray = new byte[width * colorBits];
        for (int z = 0; z < height; z++) {
            final int currentSourceZ = sourceY + z;
            final int currentDestZ = destY + z;
            final int dataY = MathUtils.moduloPositive(currentDestZ, destinationSize);

            for (int x = 0; x < width; x++) {
                final int currentSourceX = sourceX + x;
                final int color = getColor(currentSourceX, currentSourceZ);

                final int index = x * colorBits;
                rgbArray[index + 0] = (byte) (color >> 24 & 0xFF);
                rgbArray[index + 1] = (byte) (color >> 16 & 0xFF);
                rgbArray[index + 2] = (byte) (color >> 8 & 0xFF);
                if (useAlpha) {
                    rgbArray[index + 3] = (byte) (color & 0xFF);
                }
            }

            final int dataX = MathUtils.moduloPositive(destX, destinationSize);
            if (dataX + width > destinationSize) {
                final int destIndex = dataY * destinationSize * colorBits;

                destinationData.position(destIndex + dataX * colorBits);
                destinationData.put(rgbArray, 0, (destinationSize - dataX) * colorBits);

                destinationData.position(destIndex);
                destinationData.put(rgbArray, (destinationSize - dataX) * colorBits, (dataX + width - destinationSize)
                        * colorBits);
            } else {
                final int destIndex = (dataY * destinationSize + dataX) * colorBits;
                destinationData.position(destIndex);
                destinationData.put(rgbArray);
            }
        }
    }

    public static class TileLoadingData implements Callable<Boolean> {
        private final TextureSource source;
        private final byte[] data;
        private final CacheData[][] cache;
        private final int tileSize;
        private final int dataSize;

        private final SourceCacheFunction function;
        private final TextureConfiguration textureConfiguration;
        private final boolean useAlpha;

        private final int clipmapLevel;
        private final int requestedLevel;

        private final DoubleBufferedList<Region> mailBox;

        public final Tile sourceTile;
        public final Tile destTile;

        public boolean isCancelled = false;
        public Future<Boolean> future;

        public enum State {
            init, loading, finished
        }

        public State state = State.init;

        public TileLoadingData(final DoubleBufferedList<Region> mailBox, final Tile sourceTile, final Tile destTile,
                final TextureSource source, final CacheData[][] cache, final byte[] data, final int tileSize,
                final int dataSize, final SourceCacheFunction function,
                final TextureConfiguration textureConfiguration, final boolean useAlpha, final int clipmapLevel,
                final int requestedLevel) {
            this.mailBox = mailBox;

            this.sourceTile = sourceTile;
            this.destTile = destTile;

            this.source = source;
            this.cache = cache;
            this.data = data;
            this.tileSize = tileSize;
            this.dataSize = dataSize;

            this.function = function;
            this.textureConfiguration = textureConfiguration;
            this.useAlpha = useAlpha;

            this.clipmapLevel = clipmapLevel;
            this.requestedLevel = requestedLevel;
        }

        @Override
        public Boolean call() throws Exception {
            state = State.loading;

            if (isCancelled()) {
                return false;
            }

            ByteBuffer sourceData = null;
            try {
                sourceData = source.getTile(requestedLevel, sourceTile);
            } catch (final InterruptedException e) {
                // XXX: Loading can be interrupted
                return false;
            } catch (final Throwable t) {
                t.printStackTrace();
            }

            if (sourceData == null || isCancelled()) {
                return false;
            }

            final TextureStoreFormat format = textureConfiguration.getTextureDataType(source.getContributorId(
                    requestedLevel, sourceTile));
            CacheFunctionUtil.applyFunction(useAlpha, function, sourceData, data, destTile.getX(), destTile.getY(),
                    format, tileSize, dataSize);

            if (isCancelled()) {
                return false;
            }

            state = State.finished;
            cache[destTile.getX()][destTile.getY()].isValid = true;

            final Region region = new Region(clipmapLevel, sourceTile.getX() * tileSize, sourceTile.getY() * tileSize,
                    tileSize, tileSize);
            if (mailBox != null) {
                mailBox.add(region);
            }

            return true;
        }

        private boolean isCancelled() {
            if (future != null && future.isCancelled()) {
                return true;
            } else if (isCancelled) {
                return true;
            } else if (Thread.interrupted()) {
                return true;
            }
            return false;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + (destTile == null ? 0 : destTile.hashCode());
            result = prime * result + (sourceTile == null ? 0 : sourceTile.hashCode());
            return result;
        }

        @Override
        public boolean equals(final Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (!(obj instanceof TileLoadingData)) {
                return false;
            }
            final TileLoadingData other = (TileLoadingData) obj;
            if (destTile == null) {
                if (other.destTile != null) {
                    return false;
                }
            } else if (!destTile.equals(other.destTile)) {
                return false;
            }
            if (sourceTile == null) {
                if (other.sourceTile != null) {
                    return false;
                }
            } else if (!sourceTile.equals(other.sourceTile)) {
                return false;
            }
            return true;
        }

        @Override
        public String toString() {
            return "TileLoadingData [destTile=" + destTile + ", sourceTile=" + sourceTile + "]";
        }
    }

    public static class CacheData {
        public boolean isValid;

        public CacheData() {
            isValid = false;
        }
    }

    public boolean isValid() {
        int nrValid = 0;
        for (final TileLoadingData data : currentTiles) {
            if (cache[data.destTile.getX()][data.destTile.getY()].isValid) {
                nrValid++;
            }
        }
        return nrValid != 0;
    }

    public void setMailBox(final DoubleBufferedList<Region> mailBox) {
        this.mailBox = mailBox;
    }

    public SourceCacheFunction getFunction() {
        return function;
    }

    public void setFunction(final SourceCacheFunction function) {
        this.function = function;
    }

    public void shutdown() {
        exit = true;
        started = false;
    }
}
TOP

Related Classes of com.ardor3d.extension.terrain.client.TextureGridCache$TileLoadingData

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.