Something seems wrong with terrain patch normals

I’m converting my little plaything terrain quad to a terrain grid setup. But I get some really strange patterns on the terrain. Almost looks like some normals are a bit weird where the terrain patches are. Here is how it looks in my app:





The terrain quad height maps do not line up so you can see where there are two quads side by side.



Another strange thing is that the quads at 0,0,0 does not show this. Does not seem to matter if I use triplanar or not or if I have normal maps or not.



Here is a test case that reproduces the issue (you have to fly a bit to see new quads loaded).

[java]package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.export.JmeExporter;

import com.jme3.export.JmeImporter;

import com.jme3.light.AmbientLight;

import com.jme3.light.DirectionalLight;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Vector3f;

import com.jme3.renderer.RenderManager;

import com.jme3.scene.Node;

import com.jme3.terrain.geomipmap.TerrainGrid;

import com.jme3.terrain.geomipmap.TerrainGridLodControl;

import com.jme3.terrain.geomipmap.TerrainGridTileLoader;

import com.jme3.terrain.geomipmap.TerrainQuad;

import com.jme3.texture.Texture;

import com.jme3.texture.Texture.WrapMode;

import com.jme3.texture.Texture2D;

import com.jme3.texture.plugins.AWTLoader;

import java.awt.image.BufferedImage;

import java.io.IOException;



/**

  • test
  • @author normenhansen

    /

    public class Main extends SimpleApplication {



    private static final int QUAD_SIZE = 512;

    private static final int QUAD_SIZE_HALF = QUAD_SIZE / 2;

    private Vector3f lightDirection;

    private DirectionalLight sun;



    public static void main(String[] args) {

    Main app = new Main();

    app.start();

    }



    @Override

    public void simpleInitApp() {

    flyCam.setMoveSpeed(150);

    cam.setLocation(new Vector3f(0f, 100f, 0f));

    TerrainGrid grid = new TerrainGrid("Terrain grid", QUAD_SIZE / 8 + 1, QUAD_SIZE + 1, new TerrainGridTileLoader() {



    private int patchSize;

    private int quadSize;



    public TerrainQuad getTerrainQuadAt(Vector3f location) {

    TerrainQuad createQuad = createQuad(location, this.patchSize, this.quadSize);

    return createQuad;

    }



    public void setPatchSize(int patchSize) {

    this.patchSize = patchSize;

    }



    public void setQuadSize(int quadSize) {

    this.quadSize = quadSize;

    }



    public void write(JmeExporter ex) throws IOException {

    }



    public void read(JmeImporter im) throws IOException {

    }

    });



    TerrainGridLodControl lodControl = new TerrainGridLodControl(grid, this.cam);

    grid.addControl(lodControl);



    grid.setLocalScale(4f, 1f, 4f);



    rootNode.attachChild(grid);



    setupLight(rootNode);



    }



    private void setupLight(Node rootNode) {

    lightDirection = new Vector3f(1, -1, 0).normalizeLocal();

    sun = new DirectionalLight();

    sun.setDirection(lightDirection);

    sun.setColor(new ColorRGBA(0.7f, 0.7f, 0.7f, 1.0f));

    rootNode.addLight(sun);

    AmbientLight ambientLight = new AmbientLight();

    ambientLight.setColor(new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f));

    rootNode.addLight(ambientLight);

    }



    @Override

    public void simpleUpdate(float tpf) {

    }



    @Override

    public void simpleRender(RenderManager rm) {

    }



    private BufferedImage createAlphaMap() {

    BufferedImage image = new BufferedImage(QUAD_SIZE, QUAD_SIZE, BufferedImage.TYPE_INT_ARGB);

    for (int x = 0; x < QUAD_SIZE_HALF; x++) {

    for (int y = 0; y < QUAD_SIZE_HALF; y++) {



    setPixel(128, 128, 0, 0, image, x, y);

    setPixel(0, 128, 128, 0, image, x + QUAD_SIZE_HALF, y);

    setPixel(0, 0, 128, 128, image, x, y + QUAD_SIZE_HALF);

    setPixel(128, 0, 0, 128, image, x + QUAD_SIZE_HALF, y + QUAD_SIZE_HALF);

    }

    }

    return image;

    }



    private BufferedImage createSingleColor(int a, int r, int g, int b) {

    BufferedImage image = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);

    for (int y = 0; y < 32; y++) {

    for (int x = 0; x < 32; x++) {

    setPixel(a, r, g, b, image, x, y);

    }

    }

    return image;

    }



    private float[] createHeightMap() {

    float[] heightmap = new float[QUAD_SIZE * QUAD_SIZE];

    for (int y = 0; y < QUAD_SIZE; y++) {

    for (int x = 0; x < QUAD_SIZE; x++) {

    float v = FastMath.sin(((float) y / (float) QUAD_SIZE) * (float) FastMath.TWO_PI) * 128f;

    heightmap[x + (y * QUAD_SIZE)] = v;

    }

    }

    return heightmap;

    }



    private void setPixel(int alpha, int red, int green, int blue, BufferedImage image, int x, int y) {

    int rgb = ((alpha << 24) & 0xff000000)

    | ((red << 16) & 0x00ff0000)

    | ((green << 8 ) & 0x0000ff00)

    | (blue & 0x000000ff);



    image.setRGB(x, y, rgb);

    }



    private TerrainQuad createQuad(final Vector3f cell, int patchSize, int quadSize) {

    TerrainQuad terrainQuad;

    Material mat_terrain;



    /
    * 1. Create terrain material and load four textures into it. */

    mat_terrain = new Material(assetManager,

    "Common/MatDefs/Terrain/TerrainLighting.j3md");



    AWTLoader awtLoader = new AWTLoader();

    Texture alphaMap = new Texture2D(awtLoader.load(createAlphaMap(), true));



    Texture alphaTexture = new Texture2D(awtLoader.load(createSingleColor(255, 64, 64, 64), true));

    Texture redTexture = new Texture2D(awtLoader.load(createSingleColor(255, 255, 0, 0), true));

    Texture greenTexture = new Texture2D(awtLoader.load(createSingleColor(255, 0, 255, 0), true));

    Texture blueTexture = new Texture2D(awtLoader.load(createSingleColor(255, 0, 0, 255), true));



    mat_terrain.setTexture("AlphaMap", alphaMap);



    redTexture.setWrap(WrapMode.Repeat);

    mat_terrain.setTexture("DiffuseMap", redTexture);

    mat_terrain.setFloat("DiffuseMap_0_scale", 128f);



    greenTexture.setWrap(WrapMode.Repeat);

    mat_terrain.setTexture("DiffuseMap_1", greenTexture);

    mat_terrain.setFloat("DiffuseMap_1_scale", 64f);



    blueTexture.setWrap(WrapMode.Repeat);

    mat_terrain.setTexture("DiffuseMap_2", blueTexture);

    mat_terrain.setFloat("DiffuseMap_2_scale", 32f);



    alphaTexture.setWrap(WrapMode.Repeat);

    mat_terrain.setTexture("DiffuseMap_3", alphaTexture);

    mat_terrain.setFloat("DiffuseMap_3_scale", 16f);



    terrainQuad = new TerrainQuad("my terrain", patchSize, quadSize, createHeightMap());

    terrainQuad.setMaterial(mat_terrain);



    return terrainQuad;

    }

    }[/java]

That’s a bug in TerrainGrid not updating its normals, nothing to do with your code. I am working on a patch for it.

And here I thought I would have a sunday off :stuck_out_tongue:

1 Like
@Sploreg said:
That's a bug in TerrainGrid not updating its normals, nothing to do with your code. I am working on a patch for it.
And here I thought I would have a sunday off :p


I wasn't really expecting a fix that soon. Just happy that someone knows enough to find the bug :)

I just committed a fix for it, let me know if it resolves the issues you are seeing.

1 Like

That was quick. I only had 20 seconds to test before going to work but yes, issue seems resolved :slight_smile: Thanks a lot!

I’m not sure, but it seems like I might have a related problem here:



http://hub.jmonkeyengine.org/groups/contribution-depot-jme3/forum/topic/projective-texture-mapping/?topic_page=3&num=15&_wpnonce=33533801da#post-198361



Is there any way to view the terrain normals?



EDIT: seems though the normals are actually probably ok.