Jump to content
  • Advertisement
Sign in to follow this  
King of Men

OpenGL 'Stripes' in heightmap terrain

This topic is 4333 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am experimenting with a heightmap for my terrain. As a first pass, I'm just drawing a green wireframe; texturing is the next step. I've gotten it to work, more or less. The problem is that, at certain view angles, there are large black gaps in my nice green (flat) terrain, which I don't understand. Some screenies may make it clearer: Photobucket - Video and Image Hosting This is what things ought to look like. But if you change the camera azimuth angle just a touch, you get this: Photobucket - Video and Image Hosting Great big ravines! Now, the first thing I tried was to move my viewpoint to float above the landscape, looking straight down at it. When I do this, I do not see any gaps. In the screenies I'm showing here, the viewpoint is 8 units above the plane (the vertices are at integral points) looking off at a distant point, also 8 units above the landscape. Is this, by any chance, a known problem for which a known fix exists? In case it's helpful, here's my code:
import java.awt.*;
import net.java.games.jogl.*;
import java.awt.event.*;
import java.io.IOException;
import java.nio.IntBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import net.java.games.jogl.util.BufferUtils;
import net.java.games.jogl.util.GLUT;
import static java.lang.Math.*;

final class GUI extends Frame {

  private static boolean quit;
  private GLCanvas mainDisplay;
  private Rectangle mainDisplayRectangle;

  private GLCanvas statusDisplay;
  private Rectangle statusDisplayRectangle;

  // Camera-control variables
  private double polar;
  private double azimuth;  // Angle with x axis
  private double zoom;

  // Stuff for perspective
  private double displayRatio;
  private double angleOfScreen;

  // Cache for use in input
  private GL gl;
  private GLU glu;
  private GLUT glut = new GLUT();

  // Used in getting ingame coordinates from mouse clicks.
  // I don't understand why I need to cache them, but if I
  // call the getters in mouseClicked, I get all zeroes. 
  private int[]    view = new int[4];
  private double[] mode = new double[16];
  private double[] proj = new double[16];
  
  private static boolean paused = true;
  private long delay = 500;

  private Titan theTitan;
  private HeightMap myMap;

  public static void main (String[] args) {
    GUI g = new GUI();
    g.waitForInput();
    System.exit(0);
  }

  public GUI () {
    super();
    setLayout(null);
    setUndecorated(true);
  
    Rectangle bounds = getGraphicsConfiguration().getBounds();
    setSize(bounds.width, bounds.height);
    setVisible(true);

    quit = false;
    theTitan = new Titan();
    addKeyListener(theTitan);

    // TODO : Do away with hardcoding. 
    mainDisplayRectangle = new Rectangle(200, 0, bounds.width - 200, bounds.height);
    MainDisplayDrawer l = new MainDisplayDrawer();
    mainDisplay = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities());
    mainDisplay.addGLEventListener(l);
    mainDisplay.setBounds(mainDisplayRectangle);
    add(mainDisplay);

    statusDisplayRectangle = new Rectangle(0, 0, 200, bounds.height);
    StatusDisplayDrawer p = new StatusDisplayDrawer();
    statusDisplay = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities());
    statusDisplay.addGLEventListener(p);
    statusDisplay.setBounds(statusDisplayRectangle);
    add(statusDisplay);

    // TODO : Possibly in a separate class? 
    mainDisplay.addKeyListener(theTitan);
    statusDisplay.addKeyListener(theTitan);
    
    MainDisplayMouseListener disMouse = new MainDisplayMouseListener();
    mainDisplay.addMouseListener(disMouse);
  }

  public void waitForInput () {
    mainDisplay.display();
    statusDisplay.display();
    
    long elapsedTime = System.currentTimeMillis();
    long currentTime = 0;

    while (!quit) {
      currentTime = System.currentTimeMillis();
      //if (currentTime - elapsedTime < delay) continue;

      mainDisplay.display();
      statusDisplay.display();

      if (paused) continue;
      elapsedTime = System.currentTimeMillis();
    }
  }


  public static void doQuit () {quit = true;}
  public static void togglePause () {paused = !paused;}

  final class MainDisplayMouseListener implements MouseListener {
    public void mouseClicked (MouseEvent e) {}
    public void mousePressed (MouseEvent e) {}
    public void mouseReleased (MouseEvent e) {}
    public void mouseEntered (MouseEvent e) {}
    public void mouseExited (MouseEvent e) {}
  }

  final class MainDisplayDrawer implements GLEventListener {
    private byte[] testTexture;

    public MainDisplayDrawer () {}

    public void init (GLDrawable drawable) {
      gl = drawable.getGL(); 
      myMap = new HeightMap("heightmap.bmp", gl);
      gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
      gl.glEnable(gl.GL_DEPTH_TEST); 
      gl.glClearDepth(10000000);
      gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);
      gl.glViewport(0, 0, mainDisplayRectangle.width, mainDisplayRectangle.height);
      gl.glGetIntegerv(gl.GL_VIEWPORT, view);

      displayRatio = (1.0 * mainDisplayRectangle.width) / mainDisplayRectangle.height;
      angleOfScreen = 10;
    }
    
    public void display (GLDrawable drawable) {
      gl = drawable.getGL(); 
      glu = drawable.getGLU(); 

      gl.glMatrixMode(gl.GL_PROJECTION);
      gl.glLoadIdentity(); 
      glu.gluPerspective(angleOfScreen, displayRatio, 0.01, zoom);

      gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);
      gl.glMatrixMode(gl.GL_MODELVIEW);  
      gl.glLoadIdentity(); 
      theTitan.setView(glu);

      gl.glGetIntegerv(gl.GL_VIEWPORT, view);
      gl.glGetDoublev(gl.GL_PROJECTION_MATRIX, proj);
      gl.glGetDoublev(gl.GL_MODELVIEW_MATRIX, mode);

      myMap.draw(gl);
      

      if (paused) {
	gl.glColor3d(1.0, 1.0, 1.0); 
	gl.glRasterPos2d(0.5*mainDisplayRectangle.width, mainDisplayRectangle.height-15);
	glut.glutBitmapString(gl, glut.BITMAP_TIMES_ROMAN_10, "Game Paused");
      }
    }
    
    // These two methods should never be called.
    public void reshape (GLDrawable drawable, int i, int x, int width, int height) {}
    public void displayChanged (GLDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
  }


  final class StatusDisplayDrawer implements GLEventListener {
    public StatusDisplayDrawer () {}

    public void init (GLDrawable drawable) {
      GL logl = drawable.getGL(); 
      logl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
      logl.glViewport(0, 0, statusDisplayRectangle.width, statusDisplayRectangle.height);
      logl.glOrtho(0, statusDisplayRectangle.width, 0, statusDisplayRectangle.height, -1, 1);	 
    }
    
    public void display (GLDrawable drawable) {
      GL logl = drawable.getGL(); 
      logl.glClear(logl.GL_COLOR_BUFFER_BIT);
    }
    
    // These two methods should never be called.
    public void reshape (GLDrawable drawable, int i, int x, int width, int height) {}
    public void displayChanged (GLDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
  }
}


final class GLRectangle extends Rectangle {

  public GLRectangle (int ax, int ay, int aw, int ah) {
    super(ax, ay, aw, ah);
  }

 public boolean contains (int ax, int ay) {
    if (ax < x) return false;
    if (ax > x + width) return false;

    // Note kludge to convert Java to OpenGL
    if (ay > y) return false;
    if (ay < y - height) return false;
    return true; 
  }
}

class MyButton {

  private GLRectangle location;
  private byte[] picture;

  public MyButton (GLRectangle r) {
    location = r;
    picture = new byte[r.height*r.width*4];
    java.util.Arrays.fill(picture, (byte) -1);
  }

  public void setPicture (byte[] a) {
    picture = a;
  }

  public void draw (GL gl) {
    gl.glRasterPos2i(location.x, location.y);
    gl.glDrawPixels (location.width, location.height, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, picture);
  }

  public boolean contains (int ax, int ay) {
    return location.contains(ax, ay);
  }
}

final class MyButtonWithBooleanState extends MyButton {
  private byte[] truthPicture;
  private byte[] falsePicture;
  
  public MyButtonWithBooleanState (GLRectangle r) {
    super(r);
    truthPicture = new byte[r.height*r.width*4];
    falsePicture = new byte[r.height*r.width*4];

    for (int i = 0; i < truthPicture.length; i++) {
      if (i % 4 == 0) falsePicture = -1;
      else if (i % 4 == 1) truthPicture = -1;
    }
    
    setPicture(falsePicture);
  }

  public void setState (boolean r) {
    if (r) setPicture(truthPicture);
    else setPicture(falsePicture);
  }
}


final class Titan implements KeyListener {
  

  // Various body angles
  private double mainFacing;  // The way the feet are facing - same as the head if looking straight forward
  private double hipTwist;    // Azimuth angle of torso
  
  private double xpos;
  private double zpos;

  private Component head;
  private Component rightLeg;
  private Component leftLeg;
  private Component torso;
  private Component rightArm;
  private Component leftArm;

  private Component selectedComponent;

  public Titan () {
    head     = new Component(0, 8, -1);
    torso    = new Component(0, 6, 0);
    rightLeg = new Component(1, 3, 0);
    leftLeg  = new Component(-1, 3, 0);
    rightArm = new Component(1, 7, 0);
    leftArm  = new Component(-1, 7, 0);
    selectedComponent = head;
    xpos = 1530;
    zpos = 130;
  }

  public void setView (GLU glu) {
    
    double eyeX = xpos + selectedComponent.relativeX * sin(mainFacing+hipTwist);
    double eyeY = selectedComponent.relativeY;
    double eyeZ = zpos - selectedComponent.relativeZ * cos(mainFacing+hipTwist);

    double lookAtX = 100*sin(mainFacing+hipTwist+selectedComponent.traverseAzimuth);
    double lookAtY = selectedComponent.relativeY * (1+sin(selectedComponent.traversePolar));
    double lookAtZ = -100*cos(mainFacing+hipTwist+selectedComponent.traverseAzimuth);
    
    /*
    double eyeX = xpos;
    double eyeY = 100;
    double eyeZ = zpos;

    double lookAtX = xpos;
    double lookAtY = 0;
    double lookAtZ = zpos + 1;
    */
    glu.gluLookAt(eyeX, eyeY, eyeZ, 
		  lookAtX, lookAtY, lookAtZ, 
		  0, 1, 0);
  }

  public void keyPressed (KeyEvent e) {}
  public void keyTyped (KeyEvent e) {}
  public void keyReleased (KeyEvent e) {
    switch (e.getKeyCode()) {
    case KeyEvent.VK_Q :
      GUI.doQuit();
      break;
      
    case KeyEvent.VK_P :
      GUI.togglePause();
      break;

    case KeyEvent.VK_KP_LEFT :
    case KeyEvent.VK_NUMPAD4 :
    case KeyEvent.VK_LEFT :
      mainFacing -= 0.02*PI;
      break;
    case KeyEvent.VK_KP_RIGHT :
    case KeyEvent.VK_RIGHT :
    case KeyEvent.VK_NUMPAD6 :
      mainFacing += 0.02*PI;
      break;

    case KeyEvent.VK_NUMPAD8 :
    case KeyEvent.VK_KP_UP :
    case KeyEvent.VK_UP :
      selectedComponent.traversePolar += 0.02*PI;
      //zpos += 10;
      break;

    case KeyEvent.VK_NUMPAD2 :
    case KeyEvent.VK_KP_DOWN :
    case KeyEvent.VK_DOWN :
      selectedComponent.traversePolar -= 0.02*PI;
      //zpos -= 10;
      break;


    default : break;
    }
  }

  final class Component {
    private int armor;
    
    private double relativeX;  // Position relative to titan feet
    private double relativeY;
    private double relativeZ;

    private double traversePolar;
    private double traverseAzimuth;

    private double minAzimuth;
    private double maxAzimuth;
    private double minPolar;
    private double maxPolar;

    public Component (double x, double y, double z) {
      relativeX = x;
      relativeY = y;
      relativeZ = z;
    }
  }
}


final class HeightMap {  
  private int[][] displayIndexes;

  public HeightMap (String fname, GL gl) {
    displayIndexes = new int[20][16];
    byte[] map = null;
    int side = 100;
    float[][] heights = new float[side][side];
    

    int currIndex = gl.glGenLists(displayIndexes.length*displayIndexes[0].length);
    if (currIndex == 0) {
      // TODO : Error handling
      System.out.println("Problem initialising display indices");
      System.exit(1);
    }

    for (int i = 0; i < displayIndexes.length; i++) {
      for (int j = 0; j < displayIndexes.length; j++) {
	try {
	  map = ImageHandler.loadImage(fname, (side-1)*i, (side-1)*j, side, side);
	}
	catch (IOException e) {
	  // TODO : Again with the error handling - don't quite know what to do about this, though. 
	  System.out.println("Could not load " + fname);
	  e.printStackTrace();
	  System.exit(1);
	}

	for (int x = 0; x < side; x++) { 
	  for (int y = 0; y < side; y++) { 
	    int mapIndex = y*side + 4*x;
	    byte one = map[mapIndex];
	    byte two = map[mapIndex+1];
	    byte thr = map[mapIndex+2];
	    byte fou = map[mapIndex+3]; 
            // Ok, ok, it's a kludge - fix it later. 
	    if      ((one ==  -1) && (two ==   -1) && (thr ==  -1) && (fou ==  -1)) heights[x][y] = 0;  // White
	    else if ((one ==   0) && (two == -128) && (thr ==   0) && (fou ==  -1)) heights[x][y] = 1;  // Green
	    else if ((one ==  -1) && (two ==    0) && (thr ==   0) && (fou ==  -1)) heights[x][y] = 2;  // Red
	    else if ((one ==   0) && (two ==    0) && (thr ==   0) && (fou ==  -1)) heights[x][y] = 3;  // Blue
	    else if ((one ==   0) && (two ==    0) && (thr ==  -1) && (fou ==  -1)) heights[x][y] = 4;  // Black
	  }
	}

	displayIndexes[j] = currIndex;
	gl.glNewList(currIndex, gl.GL_COMPILE);
	int sid = side - 1;
	for (int x = 0; x < sid; x++) { 
	  gl.glBegin(gl.GL_LINE_STRIP);
	  gl.glVertex3f(sid*i + x + 1, heights[x+1][0], sid*j);

	  for (int y = 0; y < sid; y++) { 
	    gl.glVertex3f(sid*i + x, heights[x][y], sid*j + y);
	    gl.glVertex3f(sid*i + x, heights[x][y+1], sid*j + y + 1);
	    gl.glVertex3f(sid*i + x + 1, heights[x+1][y], sid*j + y);
	    gl.glVertex3f(sid*i + x + 1, heights[x+1][y+1], sid*j + y + 1);
	  }
	  gl.glVertex3f(sid*i + x, heights[x][sid], sid*j + sid);
	  gl.glEnd();
	}
	gl.glEndList();
	currIndex++;
      }
    }
  }

  public void draw (GL gl) {
    gl.glColor3d(0, 1, 0);
    for (int i = 0; i < displayIndexes.length; i++) {
      for (int j = 0; j < displayIndexes.length; j++) {
	gl.glCallList(displayIndexes[j]);
      }
    }
  }
}

Share this post


Link to post
Share on other sites
Advertisement
Ok, I just noticed and fixed the bug in the setView method, so now it'll look at things in a circle relative to the camera location and not the origin. But I still have the stripey bug, so that wasn't the problem. :)

Share this post


Link to post
Share on other sites
Is this terrain programming something you did yourself ? If so I know the answer (if you're using a professional terrain engine then I don't know!).

The sectors need to overlap. eg suppose you've divided your world into sectors of 20x20 vertices then if first sector is from

vector 1 to vector 20
then you've probably got the second sector from vector 21 to vector 40.

However, this means you'll have a gap or strip between vector 20 to vector 21
Where nothing is drawn.
You need to do the following:

vector 1 to vector 20
vector 20 to vector 40
vector 40 to vector 60 etc










Share this post


Link to post
Share on other sites
gl.glClearDepth(10000000); // is clamped to 1.0.
Manual: "Values specified by glClearDepth are clamped to the range [0,1]."

glu.gluPerspective(angleOfScreen, displayRatio, 0.01, zoom);
What is the value of zoom? Same as ClearDepth?
You get depth precision problems with such big values. Make it as small as possible.

You should avoid all double variants of OpenGL entry points.
Most, if not all, OpenGL implementations are implemented in float precision.

Share this post


Link to post
Share on other sites
Quote:
Original post by ade-the-heat
The sectors need to overlap. eg suppose you've divided your world into sectors of 20x20 vertices then if first sector is from

vector 1 to vector 20
then you've probably got the second sector from vector 21 to vector 40.


Well, I did have such a bug at one point, yes. I believe that's no longer the cause of my problem, however.

Quote:
gl.glClearDepth(10000000); // is clamped to 1.0.
Manual: "Values specified by glClearDepth are clamped to the range [0,1]."


Oops, thanks.

Quote:
glu.gluPerspective(angleOfScreen, displayRatio, 0.01, zoom);
What is the value of zoom? Same as ClearDepth?
You get depth precision problems with such big values. Make it as small as possible.


Actually, now you mention it, I don't seem to initialise zoom at all. That's rather odd. I guess I'd better look into that when I get home. Thanks.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!