Package com.ardor3d.extension.terrain.client

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

/**
* 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.FloatBuffer;
import java.util.Set;

import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.extension.terrain.util.Region;
import com.ardor3d.extension.terrain.util.Tile;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.Camera.FrustumIntersect;
import com.ardor3d.renderer.IndexMode;
import com.ardor3d.scenegraph.FloatBufferData;
import com.ardor3d.scenegraph.IndexBufferData;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.event.DirtyType;
import com.ardor3d.util.geom.BufferUtils;

/**
* ClipmapLevel is the visual representation of one lod level of height data.
*/
public class ClipmapLevel extends Mesh {

    /**
     * Precalculated useful variables.
     */
    private final int doubleVertexDistance; // vertexDistance * 2;
    private final int frameDistance; // (frameSize - 1) * vertexDistance

    /**
     * Gets the distance between two horizontal/vertical vertices. The finest level L = 0 has a distance of
     * vertexDistance = 1. Then every level it doubles so vertexDistance is always 2^L
     */
    private final int vertexDistance;

    /**
     * Gets framesize in number of vertices between outer border of current clip and outer border of next finer (inner)
     * clip.
     */
    private final int frameSize;

    /**
     * Gets the width of a clip in number of vertices. This is always one less than power of two (2^x - 1)
     */
    private final int clipSideSize;

    /**
     * Gets the region of the current clip.
     */
    private final Region clipRegion;
    private final Region intersectionRegion;

    /**
     * Value that indicates the height scaling. It is also represents the maximum terrain height.
     */
    private final float heightScale;

    private float heightRangeMin = 0.0f;
    private float heightRangeMax = 1.0f;

    /**
     * Index to indicate how much vertices are added to the triangle strip.
     */
    private int stripIndex = 0;

    /**
     * The used terrain heightfield. This must be set per reference so all clip levels share the same memory for that
     * variable. The values are between 0.0f and 1.0f
     */
    private final TerrainCache cache;

    /**
     * Number of components for vertices
     */
    public static final int VERT_SIZE = 4;

    /**
     * Camera frustum to test clipmap tiles against for culling
     */
    private final Camera clipmapTestFrustum;

    /**
     * Used to handle transformations of the terrain
     */
    private final Vector3 transformedFrustumPos = new Vector3();

    /**
     * Should the clipmap generate per vertex normals
     */
    private final boolean generateNormals = false;

    /**
     * Bounding box used for culling
     */
    private final BoundingBox frustumCheckBounds = new BoundingBox();

    /**
     * Possible nio speedup when storing indices
     */
    private int[] tmpIndices;

    /**
     * Should cull blocks outside camera frustum
     */
    private boolean cullingEnabled = true;

    /**
     * Creates a new clipmaplevel.
     *
     * @param levelIndex
     *            Levelindex of the clipmap. If is 0 this will be the finest level
     * @param clipSideSize
     *            Number of vertices per clipside. Must be one less than power of two.
     * @param heightScale
     *            Maximum terrainheight and heightscale
     * @param fieldsize
     *            Width and heightvalue of the heightfield
     * @param heightfield
     *            Heightvalues with a range of 0.0f - 1.0f
     * @exception Exception
     */
    public ClipmapLevel(final int levelIndex, final Camera clipmapTestFrustum, final int clipSideSize,
            final float heightScale, final TerrainCache cache) throws Exception {
        super("Clipmap Level " + levelIndex);

        // Check some exception cases
        if (levelIndex < 0) {
            throw new Exception("levelIndex must be positive");
        }
        if (!MathUtils.isPowerOfTwo(clipSideSize + 1)) {
            throw new Exception("clipSideSize must be one less than power of two");
        }

        // Apply the values
        this.clipmapTestFrustum = clipmapTestFrustum;
        this.cache = cache;

        this.heightScale = heightScale;
        this.clipSideSize = clipSideSize;

        // Calculate common variables
        vertexDistance = (int) Math.pow(2, levelIndex);
        frameSize = (clipSideSize + 1) / 4;
        doubleVertexDistance = vertexDistance * 2;
        frameDistance = (frameSize - 1) * vertexDistance;
        clipRegion = new Region(0, 0, (clipSideSize - 1) * vertexDistance, (clipSideSize - 1) * vertexDistance);
        intersectionRegion = new Region(0, 0, clipSideSize * vertexDistance, clipSideSize * vertexDistance);

        // Initialize the vertices
        initialize();
    }

    /**
     * Initializes the vertices and indices.
     */
    private void initialize() {
        getMeshData().setIndexMode(IndexMode.TriangleStrip);

        // clipSideSize is the number of vertices per clipmapside, so number of all vertices is clipSideSize *
        // clipSideSize
        final FloatBuffer vertices = BufferUtils.createVector4Buffer(clipSideSize * clipSideSize);
        getMeshData().setVertexCoords(new FloatBufferData(vertices, 4));

        if (generateNormals) {
            final FloatBuffer normals = BufferUtils.createVector3Buffer(clipSideSize * clipSideSize);
            getMeshData().setNormalCoords(new FloatBufferData(normals, 3));
        }

        // final FloatBuffer textureCoords = BufferUtils.createVector2Buffer(N * N);
        // getMeshData().setTextureBuffer(textureCoords, 0);

        final int indicesSize = 4 * (3 * frameSize * frameSize + clipSideSize * clipSideSize / 2 + 4 * frameSize - 10);
        final IndexBufferData<?> indices = BufferUtils.createIndexBufferData(indicesSize, vertices.capacity() - 1);
        tmpIndices = new int[indicesSize];
        getMeshData().setIndices(indices);

        // Go through all rows and fill them with vertexindices.
        for (int z = 0; z < clipSideSize - 1; z++) {
            fillRow(0, clipSideSize - 1, z, z + 1);
        }
    }

    public void updateCache() {
        getWorldTransform().applyInverse(clipmapTestFrustum.getLocation(), transformedFrustumPos);
        final int cx = (int) transformedFrustumPos.getX();
        final int cz = (int) transformedFrustumPos.getZ();

        // Calculate the new position
        int clipX = cx - (clipSideSize + 1) * vertexDistance / 2;
        int clipY = cz - (clipSideSize + 1) * vertexDistance / 2;

        // Calculate the modulo to doubleVertexDistance of the new position.
        // This makes sure that the current level always fits in the hole of the
        // coarser level. The gridspacing of the coarser level is vertexDistance * 2, so here doubleVertexDistance.
        final int modX = MathUtils.moduloPositive(clipX, doubleVertexDistance);
        final int modY = MathUtils.moduloPositive(clipY, doubleVertexDistance);
        clipX = clipX + doubleVertexDistance - modX;
        clipY = clipY + doubleVertexDistance - modY;

        cache.setCurrentPosition(clipX / vertexDistance, clipY / vertexDistance);

        // TODO
        cache.handleUpdateRequests();
    }

    /**
     * Update clipmap vertices
     *
     * @param center
     */
    public void updateVertices() {
        getWorldTransform().applyInverse(clipmapTestFrustum.getLocation(), transformedFrustumPos);
        final int cx = (int) transformedFrustumPos.getX();
        final int cz = (int) transformedFrustumPos.getZ();

        // Store the old position to be able to recover it if needed
        final int oldX = clipRegion.getX();
        final int oldZ = clipRegion.getY();

        // Calculate the new position
        clipRegion.setX(cx - (clipSideSize + 1) * vertexDistance / 2);
        clipRegion.setY(cz - (clipSideSize + 1) * vertexDistance / 2);

        // Calculate the modulo to doubleVertexDistance of the new position.
        // This makes sure that the current level always fits in the hole of the
        // coarser level. The gridspacing of the coarser level is vertexDistance * 2, so here doubleVertexDistance.
        final int modX = MathUtils.moduloPositive(clipRegion.getX(), doubleVertexDistance);
        final int modY = MathUtils.moduloPositive(clipRegion.getY(), doubleVertexDistance);
        clipRegion.setX(clipRegion.getX() + doubleVertexDistance - modX);
        clipRegion.setY(clipRegion.getY() + doubleVertexDistance - modY);

        // Calculate the moving distance
        final int dx = clipRegion.getX() - oldX;
        final int dz = clipRegion.getY() - oldZ;

        intersectionRegion.setX(clipRegion.getX());
        intersectionRegion.setY(clipRegion.getY());

        cache.setCurrentPosition(clipRegion.getX() / vertexDistance, clipRegion.getY() / vertexDistance);

        updateVertices(dx, dz);

        final Set<Tile> updatedTiles = cache.handleUpdateRequests();
        if (updatedTiles != null) {
            // TODO: only update what's changed
            regenerate();
        }
    }

    public void regenerate() {
        updateVertices(clipRegion.getWidth(), clipRegion.getHeight());
    }

    /**
     *
     * @param cx
     * @param cz
     */
    private void updateVertices(int dx, int dz) {
        if (dx == 0 && dz == 0) {
            return;
        }

        dx = MathUtils.clamp(dx, -clipSideSize + 1, clipSideSize - 1);
        dz = MathUtils.clamp(dz, -clipSideSize + 1, clipSideSize - 1);

        // Create some better readable variables.
        // This are just the bounds of the current level (the new region).
        final int xmin = clipRegion.getLeft() / vertexDistance;
        final int xmax = clipRegion.getRight() / vertexDistance;
        final int zmin = clipRegion.getTop() / vertexDistance;
        final int zmax = clipRegion.getBottom() / vertexDistance;

        final FloatBuffer vertices = getMeshData().getVertexBuffer();

        // Update the L shaped region.
        // This replaces the old data with the new one.
        if (dz > 0) {
            if (dx > 0) {
                cache.updateRegion(vertices, xmax - dx, zmin, dx + 1, zmax - zmin - dz + 1);
            } else if (dx < 0) {
                cache.updateRegion(vertices, xmin, zmin, -dx + 1, zmax - zmin - dz + 1);
            }

            cache.updateRegion(vertices, xmin, zmax - dz, xmax - xmin + 1, dz + 1);
        } else {
            if (dx > 0) {
                cache.updateRegion(vertices, xmax - dx, zmin - dz, dx + 1, zmax - zmin + dz + 1);
            } else if (dx < 0) {
                cache.updateRegion(vertices, xmin, zmin - dz, -dx + 1, zmax - zmin + dz + 1);
            }

            if (dz < 0) {
                cache.updateRegion(vertices, xmin, zmin, xmax - xmin + 1, -dz + 1);
            }
        }
        markDirty(DirtyType.Bounding);
    }

    /**
     * Updates the whole indexarray.
     *
     * @param nextFinerLevel
     * @param frustum
     */
    public void updateIndices(final ClipmapLevel nextFinerLevel) {
        // set the stripindex to zero. We start count vertices from here.
        // The stripindex will tell us how much of the array is used.
        stripIndex = 0;

        // MxM Block 1
        fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getTop(), clipRegion.getTop()
                + frameDistance);

        // MxM Block 2
        fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + 2 * frameDistance, clipRegion.getTop(),
                clipRegion.getTop() + frameDistance);

        // MxM Block 3
        fillBlock(clipRegion.getRight() - 2 * frameDistance, clipRegion.getRight() - frameDistance,
                clipRegion.getTop(), clipRegion.getTop() + frameDistance);

        // MxM Block 4
        fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(), clipRegion.getTop(),
                clipRegion.getTop() + frameDistance);

        // MxM Block 5
        fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getTop() + frameDistance,
                clipRegion.getTop() + 2 * frameDistance);

        // MxM Block 6
        fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(), clipRegion.getTop() + frameDistance,
                clipRegion.getTop() + 2 * frameDistance);

        // MxM Block 7
        fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getBottom() - 2
                * frameDistance, clipRegion.getBottom() - frameDistance);

        // MxM Block 8
        fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(), clipRegion.getBottom() - 2
                * frameDistance, clipRegion.getBottom() - frameDistance);

        // MxM Block 9
        fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getBottom() - frameDistance,
                clipRegion.getBottom());

        // MxM Block 10
        fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + 2 * frameDistance,
                clipRegion.getBottom() - frameDistance, clipRegion.getBottom());

        // MxM Block 11
        fillBlock(clipRegion.getRight() - 2 * frameDistance, clipRegion.getRight() - frameDistance,
                clipRegion.getBottom() - frameDistance, clipRegion.getBottom());

        // MxM Block 12
        fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(), clipRegion.getBottom() - frameDistance,
                clipRegion.getBottom());

        // Fixup Top
        fillBlock(clipRegion.getLeft() + 2 * frameDistance, clipRegion.getLeft() + 2 * frameDistance
                + doubleVertexDistance, clipRegion.getTop(), clipRegion.getTop() + frameDistance);

        // Fixup Left
        fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getTop() + 2 * frameDistance,
                clipRegion.getTop() + 2 * frameDistance + doubleVertexDistance);

        // Fixup Right
        fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(),
                clipRegion.getTop() + 2 * frameDistance, clipRegion.getTop() + 2 * frameDistance + doubleVertexDistance);

        // Fixup Bottom
        fillBlock(clipRegion.getLeft() + 2 * frameDistance, clipRegion.getLeft() + 2 * frameDistance
                + doubleVertexDistance, clipRegion.getBottom() - frameDistance, clipRegion.getBottom());

        if (nextFinerLevel != null) {
            if ((nextFinerLevel.clipRegion.getX() - clipRegion.getX()) / vertexDistance == frameSize) {
                if ((nextFinerLevel.clipRegion.getY() - clipRegion.getY()) / vertexDistance == frameSize) {
                    // Upper Left L Shape

                    // Up
                    fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getRight() - frameDistance,
                            clipRegion.getTop() + frameDistance, clipRegion.getTop() + frameDistance + vertexDistance);
                    // Left
                    fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + frameDistance
                            + vertexDistance, clipRegion.getTop() + frameDistance + vertexDistance,
                            clipRegion.getBottom() - frameDistance);
                } else {
                    // Lower Left L Shape

                    // Left
                    fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + frameDistance
                            + vertexDistance, clipRegion.getTop() + frameDistance, clipRegion.getBottom()
                            - frameDistance - vertexDistance);

                    // Bottom
                    fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getRight() - frameDistance,
                            clipRegion.getBottom() - frameDistance - vertexDistance, clipRegion.getBottom()
                                    - frameDistance);
                }
            } else {
                if ((nextFinerLevel.clipRegion.getY() - clipRegion.getY()) / vertexDistance == frameSize) {
                    // Upper Right L Shape

                    // Up
                    fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getRight() - frameDistance,
                            clipRegion.getTop() + frameDistance, clipRegion.getTop() + frameDistance + vertexDistance);
                    // Right
                    fillBlock(clipRegion.getRight() - frameDistance - vertexDistance, clipRegion.getRight()
                            - frameDistance, clipRegion.getTop() + frameDistance + vertexDistance,
                            clipRegion.getBottom() - frameDistance);
                } else {
                    // Lower Right L Shape

                    // Right
                    fillBlock(clipRegion.getRight() - frameDistance - vertexDistance, clipRegion.getRight()
                            - frameDistance, clipRegion.getTop() + frameDistance, clipRegion.getBottom()
                            - frameDistance - vertexDistance);

                    // Bottom
                    fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getRight() - frameDistance,
                            clipRegion.getBottom() - frameDistance - vertexDistance, clipRegion.getBottom()
                                    - frameDistance);
                }
            }
        }

        // Fill in the middle patch if most detailed layer
        if (nextFinerLevel == null) {
            fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + frameDistance + clipSideSize / 2,
                    clipRegion.getTop() + frameDistance, clipRegion.getTop() + frameDistance + clipSideSize / 2);

            fillBlock(clipRegion.getLeft() + frameDistance + clipSideSize / 2, clipRegion.getRight() - frameDistance,
                    clipRegion.getTop() + frameDistance, clipRegion.getTop() + frameDistance + clipSideSize / 2);

            fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + frameDistance + clipSideSize / 2,
                    clipRegion.getTop() + frameDistance + clipSideSize / 2, clipRegion.getBottom() - frameDistance);

            fillBlock(clipRegion.getLeft() + frameDistance + clipSideSize / 2, clipRegion.getRight() - frameDistance,
                    clipRegion.getTop() + frameDistance + clipSideSize / 2, clipRegion.getBottom() - frameDistance);
        }

        final IndexBufferData<?> indices = getMeshData().getIndices();
        indices.clear();
        indices.put(tmpIndices, 0, getStripIndex());
        indices.flip();
    }

    /**
     * Fills a specified area to indexarray. This will be added only after a bounding test pass.
     *
     * @param left
     * @param right
     * @param top
     * @param bottom
     */
    private void fillBlock(int left, int right, int top, int bottom) {
        if (cullingEnabled) {
            // Setup the boundingbox of the block to fill.
            // The lowest value is zero, the highest is the scalesize.
            frustumCheckBounds.setCenter((left + right) * 0.5, (heightRangeMax + heightRangeMin) * heightScale * 0.5,
                    (top + bottom) * 0.5);
            frustumCheckBounds.setXExtent((left - right) * 0.5);
            frustumCheckBounds.setYExtent((heightRangeMax - heightRangeMin) * heightScale * 0.5);
            frustumCheckBounds.setZExtent((top - bottom) * 0.5);

            frustumCheckBounds.transform(getWorldTransform(), frustumCheckBounds);

            final int state = clipmapTestFrustum.getPlaneState();

            final boolean isVisible = clipmapTestFrustum.contains(frustumCheckBounds) != FrustumIntersect.Outside;
            clipmapTestFrustum.setPlaneState(state);

            if (!isVisible) {
                return;
            }
        }

        // Same moduloprocedure as when we updated the vertices.
        // Maps the terrainposition to arrayposition.
        left = left / vertexDistance % clipSideSize;
        right = right / vertexDistance % clipSideSize;
        top = top / vertexDistance % clipSideSize;
        bottom = bottom / vertexDistance % clipSideSize;
        left += left < 0 ? clipSideSize : 0;
        right += right < 0 ? clipSideSize : 0;
        top += top < 0 ? clipSideSize : 0;
        bottom += bottom < 0 ? clipSideSize : 0;

        // Now fill the block.
        if (bottom < top) {
            // Bottom border is positioned somwhere over the top border,
            // we have a wrapover so we must split up the update in two parts.

            // Go from top border to the end of the array and update every row
            for (int z = top; z <= clipSideSize - 2; z++) {
                fillRow(left, right, z, z + 1);
            }

            // Update the wrapover row
            fillRow(left, right, clipSideSize - 1, 0);

            // Go from arraystart to the bottom border and update every row.
            for (int z = 0; z <= bottom - 1; z++) {
                fillRow(left, right, z, z + 1);
            }
        } else {
            // Top border is over the bottom border. Update from top to bottom.
            for (int z = top; z <= bottom - 1; z++) {
                fillRow(left, right, z, z + 1);
            }
        }
    }

    /**
     * Fills a strip of triangles that can be build between vertices row Zn and Zn1.
     *
     * @param startX
     *            Start x-coordinate
     * @param endX
     *            End x-coordinate
     * @param rowZ
     *            Row n
     * @param rowZPlus1
     *            Row n + 1
     */
    private void fillRow(final int startX, final int endX, final int rowZ, final int rowZPlus1) {
        addIndex(startX, rowZPlus1);
        if (startX <= endX) {
            for (int x = startX; x <= endX; x++) {
                addIndex(x, rowZPlus1);
                addIndex(x, rowZ);
            }
        } else {
            for (int x = startX; x <= clipSideSize - 1; x++) {
                addIndex(x, rowZPlus1);
                addIndex(x, rowZ);
            }
            for (int x = 0; x <= endX; x++) {
                addIndex(x, rowZPlus1);
                addIndex(x, rowZ);
            }
        }
        addIndex(endX, rowZ);
    }

    /**
     * Adds a specific index to indexarray.
     *
     * @param x
     * @param z
     */
    private void addIndex(final int x, final int z) {
        // add the index and increment counter.
        tmpIndices[stripIndex++] = x + z * clipSideSize;
    }

    /**
     * Gets the number of triangles that are visible in current frame. This changes every frame.
     */
    public int getStripIndex() {
        return stripIndex;
    }

    /**
     * @return the vertexDistance
     */
    public int getVertexDistance() {
        return vertexDistance;
    }

    public boolean isReady() {
        return cache.isValid();
    }

    public TerrainCache getCache() {
        return cache;
    }

    public void setHeightRange(final float heightRangeMin, final float heightRangeMax) {
        this.heightRangeMin = heightRangeMin;
        this.heightRangeMax = heightRangeMax;
    }

    public float getHeightRangeMax() {
        return heightRangeMax;
    }

    public float getHeightRangeMin() {
        return heightRangeMin;
    }

    public int getClipSideSize() {
        return clipSideSize;
    }

    public Region getClipRegion() {
        return clipRegion;
    }

    public Region getIntersectionRegion() {
        return intersectionRegion;
    }

    public float getHeightScale() {
        return heightScale;
    }

    public boolean isCullingEnabled() {
        return cullingEnabled;
    }

    public void setCullingEnabled(final boolean cullingEnabled) {
        this.cullingEnabled = cullingEnabled;
    }

    public void shutdown() {
        cache.shutdown();
    }
}
TOP

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

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.