Seamless Vertex Alpha Terrain

ive got something worth showing now i think :smiley: also for you guys to critique!



anyway some things i realized are messed up are, at the terrains seams you can clearly see it. I need to fix this by making sure at the edge of the touching terrains the normals are pointing in the same direction. and also i realize it crashes if you walk off the edge.



some info:

any terrain not withing reach of 1 of the current terrain you’re on is detached (not including diagnols )

there terrains texturing is built up in layers of type, using vertex alpha, my example shows grass and dirt.



heres some screenshots:



http://www.thewoops.com/bgilb/VTscreenshot1.PNG

http://www.thewoops.com/bgilb/VTscreenshot2.PNG



code:



VertexTerrainBlock.java


package VertexTerrain;

import com.jme.scene.Node;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.MaterialState;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.util.TextureManager;
//import com.jme.util.BumpMapColorController;
import com.jme.util.geom.BufferUtils;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.ImageBasedHeightMap;
import com.jme.system.DisplaySystem;

import java.util.ArrayList;
import java.net.URL;
import java.nio.FloatBuffer;

import javax.swing.*;

/**
 * Created by IntelliJ IDEA.
 * User: bglib
 * Date: Feb 20, 2007
 * Time: 3:47:16 AM
 * To change this template use File | Settings | File Templates.
 */
public class VertexTerrainBlock extends Node
{
    private ArrayList<TextureState> textureStates = new ArrayList<TextureState>();
    public ArrayList<TerrainBlock> terrainBlocks = new ArrayList<TerrainBlock>();
    private int terrainSize;
    private boolean terrainClod;
    private int numLayers=0;
    private FloatBuffer vertexBuffer;
    //private BumpMapColorController bumpMapCon;
    private int[] heights;
    String name="";
    private int gridX;
    private int gridY;

    public VertexTerrainBlock(String name, int size, boolean clod, DisplaySystem display)
    {
        this.name=name;
        terrainSize=size;
        terrainClod=clod;
        AlphaState as = display.getRenderer().createAlphaState();
                as.setBlendEnabled(true);
                as.setSrcFunction(AlphaState.SB_SRC_ALPHA);
                as.setDstFunction(AlphaState.SB_ONE_MINUS_SRC_ALPHA);
                as.setTestFunction(AlphaState.TF_GREATER);

        MaterialState ms = display.getRenderer().createMaterialState();
            ms.setColorMaterial(MaterialState.CM_DIFFUSE);
        this.setRenderState(ms);
        this.setRenderState(as);
        randomNoise();
    }
    public void randomNoise()
    {
        int[] noiseRandom = new int[(terrainSize*terrainSize)];
        for(int x = 0;x<((terrainSize)*(terrainSize));x++)
        {
            //noiseRandom[x]=(int)(Math.random()*1.5);
            noiseRandom[x]=0;
        }
        heights=noiseRandom;
    }
    public void setGridPos(int x, int y)
    {
        gridX=x;
        gridY=y;
    }
    public int getGridX()
    {
        return gridX;
    }
    public int getTerrainSize()
    {
        return terrainSize;
    }
    public int getGridY()
    {
        return gridY;
    }
    public void setHeightFromMap(String loc)
    {
        URL grayImage=VertexTerrainBlock.
            class.getClassLoader().
            getResource(loc);
        ImageBasedHeightMap ib=new ImageBasedHeightMap(
            new ImageIcon(grayImage).getImage());
        ib.load();
        for (TerrainBlock tb : terrainBlocks)
        {
            tb.setHeightMap(ib.getHeightMap());
            System.out.println(ib.getHeightMap());
            System.out.println(tb.getHeightMap());
            tb.setModelBound(new BoundingBox());
            tb.updateModelBound();
            tb.updateRenderState();
            tb.updateGeometricState(0, true);
            tb.updateWorldData(0);
            tb.updateFromHeightMap();
        }

        this.setModelBound(new BoundingBox());
        this.updateModelBound();
        this.updateRenderState();
        this.updateGeometricState(0,true);
        this.updateWorldData(0);
    }
    public float[] getColorBuffer(float alpha)
    {
        float[] tempFloat = new float[(terrainSize*terrainSize)*4];
        for(int x = 0;x<((terrainSize)*(terrainSize))*4;x+=4)
        {
            tempFloat[x]=1f;
            tempFloat[x+1]=1f;
            tempFloat[x+2]=1f;
            tempFloat[x+3]=alpha;
        }
        return tempFloat;
    }
    public void addLayer(String texture, DisplaySystem display, float startAlpha)
    {
        TerrainBlock tb=new TerrainBlock(name+ ", block: "+numLayers,terrainSize,
        new Vector3f(8,1,8),
        heights,
        new Vector3f(0,0,0),
        terrainClod);

        tb.setModelBound(new BoundingBox());
        tb.updateModelBound();
        tb.updateRenderState();

        Texture tex2 =
            TextureManager.loadTexture(VertexTerrainBlock.class.getClassLoader().getResource(
                  texture),
            Texture.MM_LINEAR,
            Texture.FM_LINEAR);
            tex2.setScale(new Vector3f(terrainSize,terrainSize,terrainSize));
            tex2.setWrap(Texture.WM_WRAP_S_WRAP_T);
      // Assign the texture to the TextureState
            TextureState ts=display.getRenderer().createTextureState();
                ts.setTexture(tex2);
                //ts.setTexture(tex2,1);
                textureStates.add(ts);
        FloatBuffer colorBuffer = BufferUtils.createFloatBuffer(getColorBuffer(startAlpha));
        tb.setColorBuffer(0,colorBuffer);
        tb.setRenderState(ts);

        terrainBlocks.add(tb);

        this.attachChild(tb);
        numLayers++;
    }
    public float getHeight(Vector3f vec)
    {
        float[] pos = new float[3];
        vec.toArray(pos);
        int size = (terrainSize-1)*8;
        int x = (int) ((pos[0])-(gridX*size))/8;//8 is the scale amount
        int y = (int) ((pos[2])-(gridY*size))/8;
        terrainBlocks.get(0).updateFromHeightMap();
        //System.out.println("Name: "+(int)vertexBuffer.get(1+(x+(terrainSize*y))*3));
        FloatBuffer vertexBuffer = terrainBlocks.get(0).getVertexBuffer(0);
        return vertexBuffer.get(1+(x+(terrainSize*y))*3);
    }
    public void setHeight(int x, int y, float amount)
    {
        for (TerrainBlock tb : terrainBlocks)
        {
            vertexBuffer = tb.getVertexBuffer(0);
            vertexBuffer.put(1 + (x + (terrainSize * y)) * 3, amount);
            tb.setHeightMapValue(x, y, (int) amount);
            tb.setVertexBuffer(0, vertexBuffer);
            tb.setModelBound(new BoundingBox());
            tb.updateModelBound();
            tb.updateRenderState();
            tb.updateGeometricState(0, true);
            //tb.updateFromHeightMap();
        }
    }
    public void setAlpha(int x, int y, int layer, float amount)
    {
        TerrainBlock tb = terrainBlocks.get(layer);
        FloatBuffer colorBuffer = tb.getColorBuffer(0);//reuse same buffer for speed
        colorBuffer.put(3+(x+(terrainSize*y))*4, amount);
        //colorBuffer.put(4, amount);
        tb.setColorBuffer(0,colorBuffer);

        tb.setModelBound(new BoundingBox());
        tb.updateModelBound();
        tb.updateRenderState();
        tb.updateGeometricState(0,true);
    }


}



SeamlessVertexTerrain.java


package VertexTerrain;

import com.jme.scene.Node;
import com.jme.math.Vector3f;
import com.jme.bounding.BoundingBox;

import java.util.ArrayList;

/**
 * Created by IntelliJ IDEA.
 * User: bglib
 * Date: Feb 23, 2007
 * Time: 12:22:09 AM
 * To change this template use File | Settings | File Templates.
 */
public class SeamlessVertexTerrain extends Node
{

    ArrayList<VertexTerrainBlock> terrains = new ArrayList<VertexTerrainBlock>();
    int curPosX=0;
    int curPosY=0;
    int widthX=0;
    int widthY=0;
    long updateDelay=System.currentTimeMillis();
    /*
    0 1 2
    3 4 5
    6 7 8
    */

    public SeamlessVertexTerrain()
    {
        this.setName("SeamlessTerrain");
    }
    public void addChild(int pos, VertexTerrainBlock vtb, int terrainSize, int x, int y)//8 is the scale size
    {
        terrains.add(pos, vtb);
        vtb.setGridPos(x,y);
        if(x>widthX)
                widthX=x;
        if(y>widthY)
                widthY=y;
        //float amnt=32;
        vtb.setLocalTranslation(new Vector3f(((terrainSize-1)*8)*x,0,((terrainSize-1)*8)*y));

        //this.attachChild(vtb);
        this.setModelBound(new BoundingBox());
        this.updateModelBound();
        this.updateRenderState();
        this.updateGeometricState(0,true);
        this.updateWorldData(0);
    }
    public void update(int delay)
    {
        if(System.currentTimeMillis()-updateDelay>=delay)
        {
            for(int x=0;x<widthX+2;x++)
            {
                for(int y=0;y<widthY+2;y++)
                {
                    if(!(x==curPosX && y==curPosY) ||
                       !(x==curPosX-1 && y==curPosY-1) ||
                       !(x==curPosX && y==curPosY-1) ||
                       !(x==curPosX+1 && y==curPosY-1) ||
                       !(x==curPosX-1 && y==curPosY) ||
                       !(x==curPosX+1 && y==curPosY) ||
                       !(x==curPosX-1 && y==curPosY+1) ||
                       !(x==curPosX && y==curPosY+1) ||
                       !(x==curPosX+1 && y==curPosY+1))
                    {
                            VertexTerrainBlock vtb = getCurrentSector(x,y);
                            if(this.hasChild(vtb))
                                this.detachChild(vtb);
                    }
                    if((x==curPosX && y==curPosY) ||
                       (x==curPosX-1 && y==curPosY-1) ||
                       (x==curPosX && y==curPosY-1) ||
                       (x==curPosX+1 && y==curPosY-1) ||
                       (x==curPosX-1 && y==curPosY) ||
                       (x==curPosX+1 && y==curPosY) ||
                       (x==curPosX-1 && y==curPosY+1) ||
                       (x==curPosX && y==curPosY+1) ||
                       (x==curPosX+1 && y==curPosY+1))
                    {
                            VertexTerrainBlock vtb = getCurrentSector(x,y);
                            if(!this.hasChild(vtb))
                            {
                                this.attachChild(vtb);
                                this.setModelBound(new BoundingBox());
                                this.updateModelBound();
                                this.updateRenderState();
                                this.updateGeometricState(0,true);
                                this.updateWorldData(0);
                            }
                    }
                }
            }
            updateDelay=System.currentTimeMillis();
        }
    }
    public VertexTerrainBlock getCurrentSector(int x, int y)
    {
        for (VertexTerrainBlock terrain : terrains)
        {
            int x2 = terrain.getGridX();
            int y2 = terrain.getGridY();
            if( (x==x2) && (y==y2) )
            {
                return terrain;
            }
        }
        return null;
    }
    public VertexTerrainBlock getCurrentSectorVTB(Vector3f position)
    {
        float[] pos = new float [3];
        position.toArray(pos);
        int x= (int)((pos[0])/248);
        int y= (int)((pos[2])/248);
        curPosX= (int)((pos[0])/248);
        curPosY= (int)((pos[2])/248);
        for (VertexTerrainBlock terrain : terrains)
        {
            int x2 = terrain.getGridX();
            int y2 = terrain.getGridY();
            if( (x==x2) && (y==y2) )
            {
                return terrain;
            }
        }
        return null;
    }
    /*public TerrainBlock equals(Node n)
    {
        VertexTerrainBlock vtb1=null;
        for(int x=0;x<terrains.size();x++)
        {
            vtb1 = terrains.get(x);
            if(n.hasChild(vtb1.terrainBlocks.get(0)))
            {
                return vtb1.terrainBlocks.get(0);
            }
        }
        return null;
    }*/
}




Also for convience i uploaded the height maps and textures to this folder:
http://www.thewoops.com/bgilb/terrain

HelloTriMesh.java ( the example )


import com.jme.app.SimpleGame;
import com.jme.scene.TriMesh;
import com.jme.scene.Node;
import com.jme.scene.shape.Pyramid;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.*;
import com.jme.math.Vector3f;
import com.jme.math.Vector2f;
import com.jme.math.Ray;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.ProceduralTextureGenerator;
import com.jmex.terrain.util.ImageBasedHeightMap;
import com.jmex.terrain.util.ProceduralSplatTextureGenerator;
import com.jmex.terrain.util.RawHeightMap;
import com.jme.renderer.ColorRGBA;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.util.geom.BufferUtils;
import com.jme.util.TextureManager;
import com.jme.util.BumpMapColorController;
import com.jme.image.Texture;
import com.jme.light.PointLight;
import com.jme.light.DirectionalLight;
import com.jme.intersection.PickResults;
import com.jme.intersection.PickData;
import com.jme.intersection.TrianglePickResults;

import javax.swing.*;
import java.net.URL;
import java.nio.FloatBuffer;

import VertexTerrain.VertexTerrainBlock;
import VertexTerrain.SeamlessVertexTerrain;

/**
 * Started Date: Jul 20, 2004<br><br>
 *
 * Demonstrates making a new TriMesh object from scratch.
 *
 * @author Jack Lindamood
 */
public class HelloTriMesh extends SimpleGame {
    public AlphaState as;
    public TextureState ts;
    public TextureState ts2;
    //public LightState ls;
    public FogState fs;
    public MaterialState ms;
    //public SkyDome dome;
    public SeamlessVertexTerrain seamlessTerrain = new SeamlessVertexTerrain();
    private Vector3f camPos = new Vector3f();

    public static void main(String[] args) {
        HelloTriMesh app = new HelloTriMesh();
        app.setDialogBehaviour(SimpleGame.ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }
    /*private void setupSkyDome() {
        dome = new SkyDome("skyDome", new Vector3f(0.0f,0.0f,0.0f), 11, 18, 850.0f);
        dome.setModelBound(new BoundingSphere());
        dome.updateModelBound();
        dome.updateRenderState();
        dome.setUpdateTime(2.0f);
        dome.setTimeWarp(180.0f);
        dome.setDay(267);
        dome.setLatitude(-22.9f);
        dome.setLongitude(-47.083f);
        dome.setStandardMeridian(-45.0f);
        dome.setSunPosition(8f);             // 5:45 am
        dome.setTurbidity(2.0f);
        dome.setSunEnabled(true);
        dome.setExposure(true, 18.0f);
        dome.setOvercastFactor(0.0f);
        dome.setGammaCorrection(2.9f);
        dome.setRootNode(rootNode);
        dome.setIntensity(1.4f);
        dome.setLocalTranslation(new Vector3f(0,-25,0));
        // setup a target to LightNode, if you dont want terrain with light's effect remove it.
        dome.setTarget(rootNode);
        rootNode.attachChild(dome);
    }*/
    protected void simpleUpdate()
    {
        camPos.x = cam.getLocation().x;
        camPos.y = (seamlessTerrain.getCurrentSectorVTB(cam.getLocation()).getHeight(cam.getLocation()))+12;
        camPos.z = cam.getLocation().z;
        cam.setLocation(camPos);
        //dome.update();
        seamlessTerrain.update(180);
    }
    protected void simpleRender() {
        //dome.render();
    }
    protected void simpleInitGame()
    {
         VertexTerrainBlock terBlock1 = new VertexTerrainBlock("terBlock1",32,false,display);
            terBlock1.addLayer("gfx/basedirt.png",display,1);
            terBlock1.addLayer("gfx/grass.png",display,0);
         VertexTerrainBlock terBlock2 = new VertexTerrainBlock("terBlock2", 32,false,display);
            terBlock2.addLayer("gfx/basedirt.png",display,1);
            terBlock2.addLayer("gfx/grass.png",display,0);
         VertexTerrainBlock terBlock3 = new VertexTerrainBlock("terBlock3", 32,false,display);
            terBlock3.addLayer("gfx/basedirt.png",display,1);
            terBlock3.addLayer("gfx/grass.png",display,0);
         VertexTerrainBlock terBlock4 = new VertexTerrainBlock("terBlock4", 32,false,display);
            terBlock4.addLayer("gfx/basedirt.png",display,1);
            terBlock4.addLayer("gfx/grass.png",display,0);
         VertexTerrainBlock terBlock5 = new VertexTerrainBlock("terBlock5", 32,false,display);
            terBlock5.addLayer("gfx/basedirt.png",display,1);
            terBlock5.addLayer("gfx/grass.png",display,0);
         VertexTerrainBlock terBlock6 = new VertexTerrainBlock("terBlock6", 32,false,display);
            terBlock6.addLayer("gfx/basedirt.png",display,1);
            terBlock6.addLayer("gfx/grass.png",display,0);
         VertexTerrainBlock terBlock7 = new VertexTerrainBlock("terBlock7", 32,false,display);
            terBlock7.addLayer("gfx/basedirt.png",display,1);
            terBlock7.addLayer("gfx/grass.png",display,0);
         for(int x = 0;x<32;x++)
         for(int y = 0;y<32;y++)
         {
                double random = Math.random();
                if(random<0.1)
                {
                    terBlock1.setAlpha(x,y,1,0);
                    terBlock2.setAlpha(x,y,1,0);
                    terBlock3.setAlpha(x,y,1,0);
                    terBlock4.setAlpha(x,y,1,0);
                    terBlock5.setAlpha(x,y,1,0);
                    terBlock6.setAlpha(x,y,1,0);
                    terBlock7.setAlpha(x,y,1,0);
                }
                else
                {
                    terBlock1.setAlpha(x,y,1,1);
                    terBlock2.setAlpha(x,y,1,1);
                    terBlock3.setAlpha(x,y,1,1);
                    terBlock4.setAlpha(x,y,1,1);
                    terBlock5.setAlpha(x,y,1,1);
                    terBlock6.setAlpha(x,y,1,1);
                    terBlock7.setAlpha(x,y,1,1);
                }
         }
         terBlock1.setHeightFromMap("gfx/height.png");
         terBlock2.setHeightFromMap("gfx/height2.png");
         terBlock3.setHeightFromMap("gfx/height3.png");
         terBlock4.setHeightFromMap("gfx/height.png");
         terBlock5.setHeightFromMap("gfx/height2.png");
         terBlock6.setHeightFromMap("gfx/height3.png");
         terBlock7.setHeightFromMap("gfx/height2.png");
         seamlessTerrain.addChild(0,terBlock1,32,0,0);
         seamlessTerrain.addChild(1,terBlock2,32,1,0);
         seamlessTerrain.addChild(2,terBlock3,32,1,1);
         seamlessTerrain.addChild(3,terBlock4,32,0,1);
         seamlessTerrain.addChild(4,terBlock5,32,0,2);
         seamlessTerrain.addChild(5,terBlock6,32,0,3);
         seamlessTerrain.addChild(6,terBlock7,32,1,2);
         rootNode.attachChild(seamlessTerrain);
         fs = display.getRenderer().createFogState();
            fs.setDensity(0.15f);
            fs.setColor(new ColorRGBA(0.69f,0.74f,0.75f,0.5f));
            fs.setEnd(160);
            fs.setStart(70);
            fs.setDensityFunction(FogState.DF_LINEAR);
            fs.setApplyFunction(FogState.AF_PER_VERTEX);
         seamlessTerrain.setRenderState(fs);
         DirectionalLight dr = new DirectionalLight();
            dr.setEnabled(true);
            dr.setDiffuse(new ColorRGBA(0.60f,0.73f,0.94f,1.0f));
            dr.setAmbient(new ColorRGBA(0.61f,0.65f,0.85f,1.0f));
            dr.setDirection(new Vector3f(-0.60f, -1.0f, -0.70f));
            dr.setAttenuate(true);
         display.getRenderer().setBackgroundColor(new ColorRGBA(0.69f,0.74f,0.75f,0.5f));
         lightState.detachAll();
            lightState.attach(dr);
            rootNode.setRenderState(lightState);
         rootNode.updateRenderState();

    }
}