Package com.ardor3d.extension.terrain.client

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

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

import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.extension.terrain.util.AbstractBresenhamTracer;
import com.ardor3d.extension.terrain.util.ClipmapTerrainPicker;
import com.ardor3d.extension.terrain.util.DoubleBufferedList;
import com.ardor3d.extension.terrain.util.Region;
import com.ardor3d.intersection.IntersectionRecord;
import com.ardor3d.intersection.Pickable;
import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Ray3;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.ContextCapabilities;
import com.ardor3d.renderer.ContextManager;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.queue.RenderBucketType;
import com.ardor3d.renderer.state.BlendState;
import com.ardor3d.renderer.state.CullState;
import com.ardor3d.renderer.state.GLSLShaderDataLogic;
import com.ardor3d.renderer.state.GLSLShaderObjectsState;
import com.ardor3d.renderer.state.MaterialState;
import com.ardor3d.renderer.state.MaterialState.MaterialFace;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.event.DirtyType;
import com.ardor3d.scenegraph.hint.DataMode;
import com.ardor3d.util.resource.ResourceLocatorTool;
import com.google.common.collect.Lists;
import com.google.common.io.InputSupplier;

/**
* An implementation of geometry clipmapping
*/
public class Terrain extends Node implements Pickable {
    /** The Constant logger. */
    private static final Logger logger = Logger.getLogger(Terrain.class.getName());

    /** Our picker. */
    private ClipmapTerrainPicker _picker = null;

    private List<ClipmapLevel> _clips;
    private int _visibleLevels = 0;
    private int _minVisibleLevel = 0;
    private final Camera _terrainCamera;
    private final int _clipSideSize;

    private final BlendState blendState;

    private boolean _initialized = false;

    /** Shader for rendering clipmap geometry with morphing. */
    private GLSLShaderObjectsState _geometryClipmapShader;

    /** Reference to the texture clipmap */
    private final List<TextureClipmap> _textureClipmaps = Lists.newArrayList();

    /** Reference to normal map */
    private TextureClipmap _normalClipmap;
    private int _normalUnit = 5;

    private final Vector3 transformedFrustumPos = new Vector3();

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

    private InputSupplier<? extends InputStream> vertexShader;
    private InputSupplier<? extends InputStream> pixelShader;

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

    final TextureState clipTextureState = new TextureState();

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

    public Terrain(final Camera camera, final List<TerrainCache> cacheList, final int clipSideSize,
            final TerrainConfiguration terrainConfiguration) {
        _terrainCamera = camera;
        _clipSideSize = clipSideSize;

        _worldBound = new BoundingBox(Vector3.ZERO, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
                Double.POSITIVE_INFINITY);

        getSceneHints().setRenderBucketType(RenderBucketType.Opaque);
        final CullState cs = new CullState();
        cs.setEnabled(true);
        cs.setCullFace(CullState.Face.Back);
        setRenderState(cs);

        final MaterialState materialState = new MaterialState();
        materialState.setAmbient(MaterialFace.FrontAndBack, new ColorRGBA(1, 1, 1, 1));
        materialState.setDiffuse(MaterialFace.FrontAndBack, new ColorRGBA(1, 1, 1, 1));
        materialState.setSpecular(MaterialFace.FrontAndBack, new ColorRGBA(1, 1, 1, 1));
        materialState.setShininess(MaterialFace.FrontAndBack, 64.0f);
        setRenderState(materialState);

        blendState = new BlendState();
        blendState.setBlendEnabled(true);
        blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
        blendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
        setRenderState(blendState);

        // getSceneHints().setLightCombineMode(LightCombineMode.Off);

        try {
            _clips = new ArrayList<ClipmapLevel>();

            final float heightScale = terrainConfiguration.getScale().getYf();

            for (int i = 0; i < cacheList.size(); i++) {
                final TerrainCache cache = cacheList.get(i);
                cache.setMailBox(mailBox);
                final ClipmapLevel clipmap = new ClipmapLevel(i, camera, clipSideSize, heightScale, cache);
                _clips.add(clipmap);
                attachChild(clipmap);

                clipmap.getSceneHints().setDataMode(DataMode.Arrays);

                // clipmap.getSceneHints().setDataMode(DataMode.VBOInterleaved);
                // final FloatBufferData interleavedData = new FloatBufferData();
                // interleavedData.setVboAccessMode(VBOAccessMode.DynamicDraw);
                // clipmap.getMeshData().setInterleavedData(interleavedData);

                // clipmap.getSceneHints().setDataMode(DataMode.VBO);
                // clipmap.getMeshData().getVertexCoords().setVboAccessMode(VBOAccessMode.DynamicDraw);
                // clipmap.getMeshData().getIndices().setVboAccessMode(VBOAccessMode.DynamicDraw);
            }
        } catch (final Exception ex) {
            ex.printStackTrace();
        }

        vertexShader = new UrlInputSupplier(ResourceLocatorTool.getClassPathResource(Terrain.class,
                "com/ardor3d/extension/terrain/texturedGeometryClipmapShader.vert"));
        pixelShader = new UrlInputSupplier(ResourceLocatorTool.getClassPathResource(Terrain.class,
                "com/ardor3d/extension/terrain/texturedGeometryClipmapShader.frag"));

        // setScale(terrainConfiguration.getScale());
        // TODO: hack. unify scale handling over cache etc
        setScale(terrainConfiguration.getScale().getX(), 1, terrainConfiguration.getScale().getZ());
        setHeightRange(terrainConfiguration.getHeightRangeMin(), terrainConfiguration.getHeightRangeMax());
    }

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

    @Override
    protected void updateChildren(final double time) {
        super.updateChildren(time);

        for (int i = _minVisibleLevel; i < _clips.size(); i++) {
            if (_clips.get(i).isReady()) {
                _visibleLevels = i;
                break;
            }
        }

        // TODO: improve calcs for removing levels based on height above terrain
        // getWorldTransform().applyInverse(_terrainCamera.getLocation(), transformedFrustumPos);
        // final float heightRangeMax = 1f;
        // if (transformedFrustumPos.getYf() > heightRangeMax) {
        // final float diff = transformedFrustumPos.getYf() - heightRangeMax;
        // final float x = (float) (diff * Math.tan(Math.toRadians(30)));
        // for (int unit = _visibleLevels; unit < _clips.size(); unit++) {
        // final float heightTest = _clipSideSize * MathUtils.pow2(unit) / x;
        // if (heightTest > 1) {
        // _visibleLevels = unit;
        // break;
        // }
        // }
        // }

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

        // Update vertices.
        for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
            _clips.get(i).updateVertices();
        }

        // Update from mailbox
        updateFromMailbox();

        // Update indices.
        for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
            if (i == _visibleLevels) {
                // Level 0 has no nested level, so pass null as parameter.
                _clips.get(i).updateIndices(null);
            } else {
                // All other levels i have the level i-1 nested in.
                _clips.get(i).updateIndices(_clips.get(i - 1));
            }
        }

        for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
            _clips.get(i).getMeshData().getVertexCoords().setNeedsRefresh(true);
            _clips.get(i).getMeshData().getIndices().setNeedsRefresh(true);
        }
    }

    @Override
    public void draw(final Renderer r) {
        updateShader(r);

        boolean first = true;
        if (_normalClipmap != null) {
            clipTextureState.setTexture(_normalClipmap.getTexture(), _normalUnit);
        }
        for (final TextureClipmap textureClipmap : _textureClipmaps) {
            clipTextureState.setTexture(textureClipmap.getTexture());
            if (first) {
                blendState.setEnabled(false);
                first = false;
            } else {
                blendState.setEnabled(true);
            }

            if (_textureClipmaps.size() > 1) {
                r.getQueue().pushBuckets();
            }

            for (int i = _clips.size() - 1; i >= 0; i--) {
                final ClipmapLevel clip = _clips.get(i);
                clip.setRenderState(clipTextureState);
            }

            if (_textureClipmaps.size() > 1) {
                _geometryClipmapShader.setUniform("scale", 1f / textureClipmap.getScale());
                _geometryClipmapShader.setUniform("textureSize", (float) textureClipmap.getTextureSize());
                _geometryClipmapShader.setUniform("texelSize", 1f / textureClipmap.getTextureSize());
                _geometryClipmapShader.setUniform("levels", (float) textureClipmap.getTextureLevels());
                _geometryClipmapShader.setUniform("validLevels", (float) textureClipmap.getValidLevels() - 1);
                _geometryClipmapShader.setUniform("showDebug", textureClipmap.isShowDebug() ? 1.0f : 0.0f);
                _geometryClipmapShader.setNeedsRefresh(true);
            }

            blendState.setNeedsRefresh(true);
            this.updateWorldRenderStates(true);

            if (!_initialized) {
                for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
                    final ClipmapLevel clip = _clips.get(i);

                    clip.getMeshData().getIndices().limit(clip.getMeshData().getIndices().capacity());
                }

                _initialized = true;
            }

            // draw levels from coarse to fine.
            for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
                final ClipmapLevel clip = _clips.get(i);

                if (clip.getStripIndex() > 0) {
                    clip.draw(r);
                }
            }

            if (_textureClipmaps.size() > 1) {
                r.renderBuckets();
                r.getQueue().popBuckets();
            }
        }
    }

    private void updateFromMailbox() {
        if (updateTimer > updateThreashold) {
            final List<Region> regionList = mailBox.switchAndGet();
            if (!regionList.isEmpty()) {
                for (int i = regionList.size() - 1; i >= 0; i--) {
                    final Region region = regionList.get(i);

                    final ClipmapLevel clip = _clips.get(region.getLevel());
                    final Region clipRegion = clip.getIntersectionRegion();

                    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 ClipmapLevel clip = _clips.get(region.getLevel());
                    final Region clipRegion = clip.getIntersectionRegion();

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

                Collections.sort(regionList, regionSorter);

                for (int i = regionList.size() - 1; i >= 0; i--) {
                    final Region region = regionList.get(i);
                    final ClipmapLevel clip = _clips.get(region.getLevel());
                    final FloatBuffer vertices = clip.getMeshData().getVertexBuffer();
                    final int vertexDistance = clip.getVertexDistance();

                    clip.getCache().updateRegion(vertices, region.getX() / vertexDistance,
                            region.getY() / vertexDistance, region.getWidth() / vertexDistance,
                            region.getHeight() / vertexDistance);
                }
            }
            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, y, width, height);
        if (!regionList.contains(region)) {
            regionList.add(region);
            recursiveAddUpdates(regionList, region.getLevel(), region.getX(), region.getY(), region.getWidth(),
                    region.getHeight());
        }
    }

    private final Vector3 _boundsCenter = new Vector3();
    private final Vector3 _boundsExtents = new Vector3();

    @Override
    public void updateWorldBound(final boolean recurse) {
        final BoundingBox worldBound = (BoundingBox) _worldBound;
        final Vector3 center = _boundsCenter.set(_terrainCamera.getLocation());
        final double distanceToEdge = _clipSideSize * MathUtils.pow2(_clips.size() - 1) * 0.5;
        final double heightScale = _clips.get(0).getHeightScale();
        final double heightMin = _clips.get(0).getHeightRangeMin() * heightScale;
        final double heightMax = _clips.get(0).getHeightRangeMax() * heightScale;

        final Vector3 extents = _boundsExtents.set(distanceToEdge, (heightMax - heightMin) * 0.5, distanceToEdge);
        worldToLocal(center, center);
        worldBound.setXExtent(extents.getX());
        worldBound.setYExtent(extents.getY());
        worldBound.setZExtent(extents.getZ());
        worldBound.setCenter(center.getX(), (heightMax + heightMin) * 0.5, center.getZ());
        worldBound.transform(_worldTransform, worldBound);
        clearDirty(DirtyType.Bounding);
    }

    /**
     * Initialize/Update shaders
     */
    public void updateShader(final Renderer r) {
        if (_geometryClipmapShader != null) {
            getWorldTransform().applyInverse(_terrainCamera.getLocation(), transformedFrustumPos);
            _geometryClipmapShader.setUniform("eyePosition", transformedFrustumPos);
            for (final TextureClipmap textureClipmap : _textureClipmaps) {
                textureClipmap.update(r, transformedFrustumPos);
            }
            if (_normalClipmap != null) {
                _normalClipmap.update(r, transformedFrustumPos);
            }

            return;
        }

        reloadShader();
    }

    public void reloadShader() {
        final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
        if (caps.isGLSLSupported()) {
            _geometryClipmapShader = new GLSLShaderObjectsState();
            try {
                _geometryClipmapShader.setVertexShader(vertexShader.getInput());
                _geometryClipmapShader.setFragmentShader(pixelShader.getInput());
            } catch (final IOException ex) {
                Terrain.logger
                        .logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
            }

            _geometryClipmapShader.setUniform("texture", 0);
            _geometryClipmapShader.setUniform("clipSideSize", (float) _clipSideSize);

            if (!_textureClipmaps.isEmpty()) {
                final TextureClipmap textureClipmap = _textureClipmaps.get(0);
                _geometryClipmapShader.setUniform("scale", 1f / textureClipmap.getScale());
                _geometryClipmapShader.setUniform("textureSize", (float) textureClipmap.getTextureSize());
                _geometryClipmapShader.setUniform("texelSize", 1f / textureClipmap.getTextureSize());

                _geometryClipmapShader.setUniform("levels", (float) textureClipmap.getTextureLevels());
                _geometryClipmapShader.setUniform("validLevels", (float) textureClipmap.getValidLevels() - 1);
                _geometryClipmapShader.setUniform("minLevel", 0f);

                _geometryClipmapShader.setUniform("showDebug", textureClipmap.isShowDebug() ? 1.0f : 0.0f);
            }

            _geometryClipmapShader.setShaderDataLogic(new GLSLShaderDataLogic() {
                public void applyData(final GLSLShaderObjectsState shader, final Mesh mesh, final Renderer renderer) {
                    if (mesh instanceof ClipmapLevel) {
                        shader.setUniform("vertexDistance", (float) ((ClipmapLevel) mesh).getVertexDistance());
                    }
                }
            });

            applyToClips();

            for (final TextureClipmap textureClipmap : _textureClipmaps) {
                textureClipmap.setShaderState(_geometryClipmapShader);
            }

            if (_normalClipmap != null) {
                _normalClipmap.setShaderState(_geometryClipmapShader);
            }

            updateWorldRenderStates(false);
        }
    }

    protected void applyToClips() {
        for (int i = _clips.size() - 1; i >= 0; i--) {
            final ClipmapLevel clip = _clips.get(i);
            clip.setRenderState(_geometryClipmapShader);
        }
    }

    public void regenerate(final Renderer renderer) {
        for (int i = _clips.size() - 1; i >= 0; i--) {
            if (!_clips.get(i).isReady()) {
                _visibleLevels = i + 1;
                break;
            }
        }

        // Update vertices.
        for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
            _clips.get(i).regenerate();
        }

        // Update indices.
        for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
            if (i == _visibleLevels) {
                // Level 0 has no nested level, so pass null as parameter.
                _clips.get(i).updateIndices(null);
            } else {
                // All other levels i have the level i-1 nested in.
                _clips.get(i).updateIndices(_clips.get(i - 1));
            }
        }

        for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
            _clips.get(i).getMeshData().getVertexCoords().setNeedsRefresh(true);
            _clips.get(i).getMeshData().getIndices().setNeedsRefresh(true);
        }

        for (final TextureClipmap textureClipmap : _textureClipmaps) {
            textureClipmap.regenerate(renderer);
        }

        if (_normalClipmap != null) {
            _normalClipmap.regenerate(renderer);
        }
    }

    /**
     * @return the visibleLevels
     */
    public int getVisibleLevels() {
        return _visibleLevels;
    }

    /**
     * @param visibleLevels
     *            the visibleLevels to set
     */
    public void setVisibleLevels(final int visibleLevels) {
        _visibleLevels = visibleLevels;
    }

    public void setHeightRange(final float heightRangeMin, final float heightRangeMax) {
        for (int i = _clips.size() - 1; i >= 0; i--) {
            final ClipmapLevel clip = _clips.get(i);
            clip.setHeightRange(heightRangeMin, heightRangeMax);
        }
    }

    public void setCullingEnabled(final boolean cullingEnabled) {
        for (int i = _clips.size() - 1; i >= 0; i--) {
            final ClipmapLevel clip = _clips.get(i);
            clip.setCullingEnabled(cullingEnabled);
        }
    }

    public void makePickable(final Class<? extends AbstractBresenhamTracer> tracerClass, final int maxChecks,
            final Vector3 initialSpacing) throws InstantiationException, IllegalAccessException {
        // init the terrain picker
        _picker = new ClipmapTerrainPicker(_clips, tracerClass, maxChecks, initialSpacing);
    }

    public TextureClipmap getTextureClipmap() {
        return _textureClipmaps.get(0);
    }

    public List<TextureClipmap> getTextureClipmaps() {
        return _textureClipmaps;
    }

    public GLSLShaderObjectsState getGeometryClipmapShader() {
        return _geometryClipmapShader;
    }

    public void setGeometryClipmapShader(final GLSLShaderObjectsState shaderState) {
        _geometryClipmapShader = shaderState;

        applyToClips();

        for (final TextureClipmap textureClipmap : _textureClipmaps) {
            textureClipmap.setShaderState(_geometryClipmapShader);
        }

        if (_normalClipmap != null) {
            _normalClipmap.setShaderState(_geometryClipmapShader);
        }
    }

    public ClipmapTerrainPicker getPicker() {
        return _picker;
    }

    @Override
    public boolean supportsBoundsIntersectionRecord() {
        // for now we are not compatible with bounding volume picks
        return false;
    }

    @Override
    public boolean supportsPrimitivesIntersectionRecord() {
        return true;
    }

    @Override
    public boolean intersectsWorldBound(final Ray3 ray) {
        // XXX: could optimize this by grabbing edges of terrain and checking if we are outside of that...
        // for now we just return true.
        return true;
    }

    @Override
    public IntersectionRecord intersectsWorldBoundsWhere(final Ray3 ray) {
        // for now we are not compatible with bounding volume picks
        return null;
    }

    @Override
    public IntersectionRecord intersectsPrimitivesWhere(final Ray3 ray) {
        if (_picker != null) {
            final Vector3 normalStore = new Vector3();
            final Vector3 intersect = _picker.getTerrainIntersection(getWorldTransform(), _terrainCamera.getLocation(),
                    ray, null, normalStore);
            if (intersect != null) {
                final double distance = intersect.distance(ray.getOrigin());
                final IntersectionRecord record = new IntersectionRecord(new double[] { distance },
                        new Vector3[] { intersect }, new Vector3[] { normalStore }, null);
                return record;
            }
        }
        return null;
    }

    public List<ClipmapLevel> getClipmaps() {
        return _clips;
    }

    public void setVertexShader(final InputSupplier<? extends InputStream> vertexShader) {
        this.vertexShader = vertexShader;
    }

    public void setPixelShader(final InputSupplier<? extends InputStream> pixelShader) {
        this.pixelShader = pixelShader;
    }

    public void addTextureClipmap(final TextureClipmap textureClipmap) {
        _textureClipmaps.add(textureClipmap);
    }

    /**
     * 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 >= _clips.size()) {
            _minVisibleLevel = _clips.size() - 1;
        } else {
            _minVisibleLevel = level;
        }
    }

    public int getMinVisibleLevel() {
        return _minVisibleLevel;
    }

    /**
     * convenience function to set minimum (highest resolution) texture clipmap level on all TextureClipmaps held by
     * this terrain
     */
    public void setTextureMinVisibleLevel(final int level) {
        for (final TextureClipmap tc : _textureClipmaps) {
            tc.setMinVisibleLevel(level);
        }
    }

    public int getTextureMinVisibleLevel() {
        if (!_textureClipmaps.isEmpty()) {
            return _textureClipmaps.get(0).getMinVisibleLevel();
        }
        return 0;
    }

    public float getHeightAt(final double x, final double z) {
        final Vector3 heightCalc = new Vector3(x, 0, z);
        worldToLocal(heightCalc, heightCalc);
        final float height = getClipmaps().get(0).getCache().getSubHeight(heightCalc.getXf(), heightCalc.getZf());
        heightCalc.set(x, height, z);
        localToWorld(heightCalc, heightCalc);
        return heightCalc.getYf();
    }

    public void shutdown() {
        for (final TextureClipmap textureClipmap : _textureClipmaps) {
            textureClipmap.shutdown();
        }
        for (final ClipmapLevel terrainClipmap : _clips) {
            terrainClipmap.shutdown();
        }
        if (_normalClipmap != null) {
            _normalClipmap.shutdown();
        }
    }

    public TextureState getClipTextureState() {
        return clipTextureState;
    }

    public void setNormalClipmap(final TextureClipmap normalClipmap) {
        _normalClipmap = normalClipmap;
    }

    public TextureClipmap getNormalClipmap() {
        return _normalClipmap;
    }

    public int getNormalUnit() {
        return _normalUnit;
    }

    public void setNormalUnit(final int unit) {
        _normalUnit = unit;
    }
}
TOP

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

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.