Package com.ardor3d.extension.terrain.client

Source Code of com.ardor3d.extension.terrain.client.TextureClipmap

/**
* 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.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ardor3d.extension.terrain.util.DoubleBufferedList;
import com.ardor3d.extension.terrain.util.LevelData;
import com.ardor3d.extension.terrain.util.Region;
import com.ardor3d.extension.terrain.util.Tile;
import com.ardor3d.image.Image;
import com.ardor3d.image.ImageDataFormat;
import com.ardor3d.image.PixelDataType;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture3D;
import com.ardor3d.image.Texture.MagnificationFilter;
import com.ardor3d.image.Texture.MinificationFilter;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Vector3;
import com.ardor3d.math.type.ReadOnlyVector3;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.state.GLSLShaderObjectsState;
import com.ardor3d.util.TextureKey;
import com.ardor3d.util.geom.BufferUtils;
import com.ardor3d.util.resource.ResourceLocatorTool;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

/**
* An implementation of texture clipmapping
*/
public class TextureClipmap {
    /** The Constant logger. */
    private static final Logger logger = Logger.getLogger(TextureClipmap.class.getName());

    private final int textureSize;
    private final int textureLevels;
    private final int validLevels;
    private int currentShownLevels;
    private int minVisibleLevel = 0;

    private float scale = 1f;

    private Texture3D textureClipmap;
    private GLSLShaderObjectsState textureClipmapShader;

    private final List<LevelData> levelDataList = Lists.newArrayList();

    private final FloatBuffer sliceDataBuffer;

    private final Vector3 eyePosition = new Vector3();

    private boolean showDebug = false;

    private final List<TextureCache> cacheList;

    private final DoubleBufferedList<Region> mailBox = new DoubleBufferedList<Region>();

    private final boolean useAlpha;
    private final int colorBits;

    /** Timers for mailbox updates */
    private long oldTime = 0;
    private long updateTimer = 0;
    private final long updateThreashold = 300;

    private final Comparator<Region> regionSorter = new Comparator<Region>() {
        @Override
        public int compare(final Region r1, final Region r2) {
            return r1.getLevel() - r2.getLevel();
        }
    };

    public TextureClipmap(final List<TextureCache> cacheList, final int textureSize,
            final TextureConfiguration textureConfiguration) {
        this.cacheList = cacheList;
        this.textureSize = textureSize;
        validLevels = cacheList.size();
        useAlpha = textureConfiguration.isUseAlpha();
        colorBits = useAlpha ? 4 : 3;

        for (final TextureCache cache : cacheList) {
            cache.setMailBox(mailBox);
        }

        textureLevels = roundUpPowerTwo(validLevels);
        scale = textureConfiguration.getTextureDensity() * textureSize / 128;

        TextureClipmap.logger.info("Texture size: " + textureSize);
        TextureClipmap.logger.info("ValidLevels: " + validLevels);
        TextureClipmap.logger.info("3D Texture depth: " + textureLevels);

        sliceDataBuffer = BufferUtils.createFloatBuffer(textureLevels * 2);

        for (int i = 0; i < validLevels; i++) {
            levelDataList.add(new LevelData(i, textureSize));
        }

        createTexture();
    }

    private final List<Long> timers = Lists.newArrayList();

    public void update(final Renderer renderer, final ReadOnlyVector3 position) {
        eyePosition.set(position);
        textureClipmapShader.setUniform("eyePosition", eyePosition);
        eyePosition.multiplyLocal(textureSize / (scale * 4f * 32f));

        for (int unit = minVisibleLevel; unit < validLevels; unit++) {
            if (cacheList.get(unit).isValid()) {
                currentShownLevels = unit;
                break;
            }
        }

        // TODO: improve calcs for removing levels based on height above terrain
        // if (eyePosition.getYf() > heightRangeMax) {
        // final float diff = eyePosition.getYf() - heightRangeMax;
        // final float x = (float) (diff * Math.tan(Math.toRadians(80)));
        // for (int unit = currentShownLevels; unit < validLevels; unit++) {
        // final float heightTest = scale * textureSize * MathUtils.pow2(unit) / x;
        // if (heightTest > 1) {
        // currentShownLevels = unit;
        // break;
        // }
        // }
        // }

        textureClipmapShader.setUniform("minLevel", (float) currentShownLevels);

        if (timers.size() < currentShownLevels) {
            for (int unit = 0; unit < currentShownLevels; unit++) {
                timers.add(System.currentTimeMillis());
            }
        }
        for (int unit = 0; unit < currentShownLevels; unit++) {
            final long t = System.currentTimeMillis() - timers.get(unit);
            if (t > 500) {
                timers.set(unit, System.currentTimeMillis());

                float x = eyePosition.getXf();
                float y = eyePosition.getZf();

                final int exp2 = MathUtils.pow2(unit);
                x /= exp2;
                y /= exp2;

                final int offX = MathUtils.floor(x);
                final int offY = MathUtils.floor(y);

                final LevelData levelData = levelDataList.get(unit);

                if (levelData.x != offX || levelData.y != offY) {
                    final TextureCache cache = cacheList.get(unit);
                    cache.setCurrentPosition(offX, offY);
                }
            }
        }

        // final long t = System.nanoTime();
        for (int unit = validLevels - 1; unit >= currentShownLevels; unit--) {
            float x = eyePosition.getXf();
            float y = eyePosition.getZf();

            final int exp2 = MathUtils.pow2(unit);
            x /= exp2;
            y /= exp2;

            final int offX = MathUtils.floor(x);
            final int offY = MathUtils.floor(y);

            final LevelData levelData = levelDataList.get(unit);

            final TextureCache cache = cacheList.get(unit);
            if (levelData.x != offX || levelData.y != offY) {
                cache.setCurrentPosition(offX, offY);
                updateLevel(renderer, levelData, offX, offY);
            }

            final Set<Tile> updatedTiles = cache.handleUpdateRequests();
            if (updatedTiles != null) {
                final int sX = offX - textureSize / 2;
                final int sY = offY - textureSize / 2;

                // System.out.println(unit + "[" + sX + "," + sY + "]: " + updatedTiles);

                // TODO: this should update only what was changed
                updateQuick(renderer, levelData, textureSize + 1, textureSize + 1, sX, sY, levelData.offsetX,
                        levelData.offsetY, textureSize, textureSize);
            }

            x = MathUtils.moduloPositive(x, 2);
            y = MathUtils.moduloPositive(y, 2);

            int shiftX = levelData.x;
            int shiftY = levelData.y;
            shiftX = MathUtils.moduloPositive(shiftX, 2);
            shiftY = MathUtils.moduloPositive(shiftY, 2);

            x -= shiftX;
            y -= shiftY;

            x += levelData.offsetX;
            y += levelData.offsetY;

            x /= textureSize;
            y /= textureSize;

            sliceDataBuffer.put(unit * 2, x);
            sliceDataBuffer.put(unit * 2 + 1, y);
        }

        sliceDataBuffer.rewind();
        textureClipmapShader.setUniform("sliceOffset", sliceDataBuffer, 2);

        updateFromMailbox(renderer);
    }

    private void updateFromMailbox(final Renderer renderer) {
        if (updateTimer > updateThreashold) {
            final List<Region> regionList = mailBox.switchAndGet();
            if (!regionList.isEmpty()) {
                for (int unit = validLevels - 1; unit >= 0; unit--) {
                    final LevelData levelData = levelDataList.get(unit);
                    // final int pow = (int) Math.pow(2, unit);
                    final int sX = levelData.x - textureSize / 2;
                    final int sY = levelData.y - textureSize / 2;
                    levelData.clipRegion.setX(sX);
                    levelData.clipRegion.setY(sY);
                }

                for (int i = regionList.size() - 1; i >= 0; i--) {
                    final Region region = regionList.get(i);
                    final Region clipRegion = levelDataList.get(region.getLevel()).clipRegion;

                    if (clipRegion.intersects(region)) {
                        clipRegion.intersection(region);
                    } else {
                        regionList.remove(i);
                    }
                }

                Collections.sort(regionList, regionSorter);

                final int start = regionList.size() - 1;
                for (int i = start; i >= 0; i--) {
                    final Region region = regionList.get(i);

                    recursiveAddUpdates(regionList, region.getLevel(), region.getX(), region.getY(), region.getWidth(),
                            region.getHeight());
                }

                for (int i = regionList.size() - 1; i >= 0; i--) {
                    final Region region = regionList.get(i);
                    final Region clipRegion = levelDataList.get(region.getLevel()).clipRegion;

                    if (clipRegion.intersects(region)) {
                        clipRegion.intersection(region);
                    } else {
                        regionList.remove(i);
                    }
                }

                Collections.sort(regionList, regionSorter);

                final Set<Integer> affectedUnits = Sets.newHashSet();
                for (int i = regionList.size() - 1; i >= 0; i--) {
                    final Region region = regionList.get(i);

                    final int unit = region.getLevel();
                    affectedUnits.add(unit);

                    final LevelData levelData = levelDataList.get(unit);
                    final TextureCache cache = cacheList.get(unit);
                    final ByteBuffer imageDestination = levelData.sliceData;

                    final int sX = region.getX();
                    final int sY = region.getY();
                    int dX = region.getX() + textureSize / 2;
                    int dY = region.getY() + textureSize / 2;
                    dX = MathUtils.moduloPositive(dX, textureSize);
                    dY = MathUtils.moduloPositive(dY, textureSize);

                    cache.updateRegion(imageDestination, sX, sY, dX + 1, dY + 1, region.getWidth(), region.getHeight());
                }

                for (final int unit : affectedUnits) {
                    final LevelData levelData = levelDataList.get(unit);
                    final ByteBuffer imageDestination = levelData.sliceData;

                    // TODO: only update subpart
                    imageDestination.rewind();
                    renderer.updateTexture3DSubImage(textureClipmap, 0, 0, unit, textureSize, textureSize, 1,
                            imageDestination, 0, 0, 0, textureSize, textureSize);
                }
            }
            updateTimer %= updateThreashold;
        }
        final long time = System.currentTimeMillis();
        updateTimer += time - oldTime;
        oldTime = time;
    }

    private void recursiveAddUpdates(final List<Region> regionList, final int level, final int x, final int y,
            final int width, final int height) {
        if (level == 0) {
            return;
        }

        final Region region = new Region(level - 1, x * 2, y * 2, width * 2, height * 2);
        if (!regionList.contains(region)) {
            regionList.add(region);
            recursiveAddUpdates(regionList, region.getLevel(), region.getX(), region.getY(), region.getWidth(), region
                    .getHeight());
        }
    }

    private void updateLevel(final Renderer renderer, final LevelData levelData, final int x, final int y) {
        final int diffX = x - levelData.x;
        final int diffY = y - levelData.y;
        levelData.x = x;
        levelData.y = y;

        final int sX = x - textureSize / 2;
        final int sY = y - textureSize / 2;

        levelData.offsetX += diffX;
        levelData.offsetY += diffY;
        levelData.offsetX = MathUtils.moduloPositive(levelData.offsetX, textureSize);
        levelData.offsetY = MathUtils.moduloPositive(levelData.offsetY, textureSize);

        updateQuick(renderer, levelData, diffX, diffY, sX, sY, levelData.offsetX, levelData.offsetY, textureSize,
                textureSize);
    }

    public void regenerate(final Renderer renderer) {
        for (int unit = validLevels - 1; unit >= 0; unit--) {
            float x = eyePosition.getXf();
            float y = eyePosition.getZf();

            final int exp2 = (int) Math.pow(2, unit);
            x /= exp2;
            y /= exp2;

            final int offX = MathUtils.floor(x);
            final int offY = MathUtils.floor(y);

            final LevelData levelData = levelDataList.get(unit);

            final int sX = offX - textureSize / 2;
            final int sY = offY - textureSize / 2;

            updateQuick(renderer, levelData, textureSize + 1, textureSize + 1, sX, sY, levelData.offsetX,
                    levelData.offsetY, textureSize, textureSize);
        }
    }

    private void updateQuick(final Renderer renderer, final LevelData levelData, final int diffX, final int diffY,
            int sX, int sY, int dX, int dY, int width, int height) {
        final int unit = levelData.unit;
        final ByteBuffer imageDestination = levelData.sliceData;

        final TextureCache cache = cacheList.get(unit);

        if (Math.abs(diffX) > textureSize || Math.abs(diffY) > textureSize) {
            // Copy the whole slice
            cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height);

            imageDestination.rewind();
            renderer.updateTexture3DSubImage(textureClipmap, 0, 0, unit, textureSize, textureSize, 1, imageDestination,
                    0, 0, 0, textureSize, textureSize);
        } else if (diffX != 0 && diffY != 0) {
            // Copy three rectangles. Horizontal, vertical and corner

            final int tmpSX = sX;
            final int tmpDX = dX;
            final int tmpWidth = width;

            // Vertical
            if (diffX > 0) {
                sX = sX + textureSize - diffX;
                dX = dX - diffX;
            }
            width = Math.abs(diffX);

            cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height);

            dX = MathUtils.moduloPositive(dX, textureSize);
            if (dX + width > textureSize) {
                int dX1 = dX;
                int width1 = textureSize - dX;

                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, dX1, 0, unit, width1, textureSize, 1,
                        imageDestination, dX1, 0, 0, textureSize, textureSize);

                dX1 = 0;
                width1 = width - width1;

                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, dX1, 0, unit, width1, textureSize, 1,
                        imageDestination, dX1, 0, 0, textureSize, textureSize);
            } else {
                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, dX, 0, unit, width, textureSize, 1, imageDestination,
                        dX, 0, 0, textureSize, textureSize);
            }

            sX = tmpSX;
            dX = tmpDX;
            width = tmpWidth;

            // Horizontal
            if (diffY > 0) {
                sY = sY + textureSize - diffY;
                dY = dY - diffY;
            }
            height = Math.abs(diffY);

            cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height);

            dY = MathUtils.moduloPositive(dY, textureSize);
            if (dY + height > textureSize) {
                int dY1 = dY;
                int height1 = textureSize - dY;

                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, 0, dY1, unit, textureSize, height1, 1,
                        imageDestination, 0, dY1, 0, textureSize, textureSize);

                dY1 = 0;
                height1 = height - height1;

                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, 0, dY1, unit, textureSize, height1, 1,
                        imageDestination, 0, dY1, 0, textureSize, textureSize);
            } else {
                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, 0, dY, unit, textureSize, height, 1, imageDestination,
                        0, dY, 0, textureSize, textureSize);
            }
        } else if (diffX != 0) {
            // Copy vertical only
            if (diffX > 0) {
                sX = sX + textureSize - diffX;
                dX = dX - diffX;
            }
            width = Math.abs(diffX);

            cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height);

            dX = MathUtils.moduloPositive(dX, textureSize);
            if (dX + width > textureSize) {
                int dX1 = dX;
                int width1 = textureSize - dX;

                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, dX1, 0, unit, width1, textureSize, 1,
                        imageDestination, dX1, 0, 0, textureSize, textureSize);

                dX1 = 0;
                width1 = width - width1;

                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, dX1, 0, unit, width1, textureSize, 1,
                        imageDestination, dX1, 0, 0, textureSize, textureSize);
            } else {
                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, dX, 0, unit, width, textureSize, 1, imageDestination,
                        dX, 0, 0, textureSize, textureSize);
            }
        } else if (diffY != 0) {
            // Copy horizontal only
            if (diffY > 0) {
                sY = sY + textureSize - diffY;
                dY = dY - diffY;
            }
            height = Math.abs(diffY);

            cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height);

            dY = MathUtils.moduloPositive(dY, textureSize);
            if (dY + height > textureSize) {
                int dY1 = dY;
                int height1 = textureSize - dY;

                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, 0, dY1, unit, textureSize, height1, 1,
                        imageDestination, 0, dY1, 0, textureSize, textureSize);

                dY1 = 0;
                height1 = height - height1;

                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, 0, dY1, unit, textureSize, height1, 1,
                        imageDestination, 0, dY1, 0, textureSize, textureSize);
            } else {
                imageDestination.rewind();
                renderer.updateTexture3DSubImage(textureClipmap, 0, dY, unit, textureSize, height, 1, imageDestination,
                        0, dY, 0, textureSize, textureSize);
            }
        }
    }

    public Texture getTexture() {
        return textureClipmap;
    }

    public void reloadShader() {
        textureClipmapShader = new GLSLShaderObjectsState();
        try {
            textureClipmapShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(TextureClipmap.class,
                    "com/ardor3d/extension/terrain/textureClipmapShader.vert"));
            textureClipmapShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
                    TextureClipmap.class, "com/ardor3d/extension/terrain/textureClipmapShader.frag"));
        } catch (final IOException ex) {
            TextureClipmap.logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.",
                    ex);
        }
        textureClipmapShader.setUniform("texture", 0);

        textureClipmapShader.setUniform("scale", 1f / scale);
        textureClipmapShader.setUniform("textureSize", (float) textureSize);
        textureClipmapShader.setUniform("texelSize", 1f / textureSize);

        textureClipmapShader.setUniform("levels", (float) textureLevels);
        textureClipmapShader.setUniform("validLevels", (float) validLevels - 1);
        textureClipmapShader.setUniform("minLevel", 0f);

        textureClipmapShader.setUniform("showDebug", showDebug ? 1.0f : 0.0f);
    }

    public GLSLShaderObjectsState getShaderState() {
        if (textureClipmapShader == null) {
            reloadShader();
        }
        return textureClipmapShader;
    }

    public void setShaderState(final GLSLShaderObjectsState textureClipmapShader) {
        this.textureClipmapShader = textureClipmapShader;
    }

    public static int clamp(final int x, final int low, final int high) {
        return x < low ? low : x > high ? high : x;
    }

    private Texture createTexture() {
        textureClipmap = new Texture3D();
        textureClipmap.setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps);
        textureClipmap.setMagnificationFilter(MagnificationFilter.NearestNeighbor);
        // textureClipmap.setMinificationFilter(MinificationFilter.BilinearNoMipMaps);
        // textureClipmap.setMagnificationFilter(MagnificationFilter.Bilinear);
        final Image img = new Image();
        img.setWidth(textureSize);
        img.setHeight(textureSize);
        img.setDepth(textureLevels);
        img.setDataFormat(useAlpha ? ImageDataFormat.RGBA : ImageDataFormat.RGB);
        img.setDataType(PixelDataType.UnsignedByte);
        textureClipmap.setTextureKey(TextureKey.getRTTKey(textureClipmap.getMinificationFilter()));

        for (int l = 0; l < textureLevels; l++) {
            final ByteBuffer sliceData = BufferUtils.createByteBuffer(textureSize * textureSize * colorBits);
            img.setData(l, sliceData);
            if (l < validLevels) {
                levelDataList.get(l).sliceData = sliceData;
            }
        }
        textureClipmap.setImage(img);

        return textureClipmap;
    }

    public float getScale() {
        return scale;
    }

    public void setScale(final float scale) {
        this.scale = scale;
    }

    private int roundUpPowerTwo(int v) {
        v--;
        v |= v >> 1;
        v |= v >> 2;
        v |= v >> 4;
        v |= v >> 8;
        v |= v >> 16;
        v++;
        return v;
    }

    public boolean isShowDebug() {
        return showDebug;
    }

    public void setShowDebug(final boolean showDebug) {
        this.showDebug = showDebug;
    }

    public int getTextureSize() {
        return textureSize;
    }

    public int getTextureLevels() {
        return textureLevels;
    }

    public int getValidLevels() {
        return validLevels;
    }

    /**
     * set the minimum (highest resolution) clipmap level visible
     *
     * @param level
     *            clamped to valid range
     */
    public void setMinVisibleLevel(final int level) {
        if (level < 0) {
            minVisibleLevel = 0;
        } else if (level >= validLevels) {
            minVisibleLevel = validLevels - 1;
        } else {
            minVisibleLevel = level;
        }
    }

    public int getMinVisibleLevel() {
        return minVisibleLevel;
    }

    public void shutdown() {
        for (final TextureCache cache : cacheList) {
            cache.shutdown();
        }
    }
}
TOP

Related Classes of com.ardor3d.extension.terrain.client.TextureClipmap

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.