Alex Saint Croix

Isometric terrain engine written in Scala and LWJGL.  First screenshot of 2nd generation engine still lacks many of the terrain processing features of my first cut, and the tiles in this screenshot are positioned manually just so I could get a screenshot.  Surface colors are determined by surface slope.

Eye candy for Isometric, Terrain Engine, Scala, LWJGL,

Isometric terrain engine written in Scala and LWJGL.  First screenshot of 2nd generation engine still lacks many of the terrain processing features of my first cut, and the tiles in this screenshot are positioned manually just so I could get a screenshot.  Surface colors are determined by surface slope.

Last week I moved 700 miles across the country, so have had some time to think about how to proceed with development.

I’ve decided to reevaluate my terrain tileforms.  In my last iteration I was having lots of problems with lizard-scale type surface textures on diagonally sloping terrain, which is not what I wanted to see.  My work right now is to essentially turn an isometric game board into a height-varying terrain board that more closely approximates natural landscapes, saving myself the task of having to design faux hill and cliff artwork, and speeding up art creation later on, as well as adding an interesting strategic element to the game.

I want to strike a balance between simulation and game.  In a strategic game such as one I’m imagining, the terrain is one of the most important features, and perhaps the single greatest factor in strategic decision making.  So, getting terrain right is crucial to the success of the project.  Building a system that has inherent variety, mutability (via terraforming or other more violent types of deformation) has to be balanced against complexity.  Not just complexity in programming, but also in player perception of terrain.

On a game board, the terrain is simple and easy to wrap one’s mind around.  It’s a square or hex, potentially of several different types.  Settlers of Catan is a good example of this.  There are different terrain types, each represented by configurable hex tiles.  From the simple arrangements of the terrain, strategies are born.  Different terrain types have different resources, and affect movement in different ways.

In my current design (no screenshots yet, sorry!) any given square terrain tile can take 175 different tileforms.  This number isn’t actually complete either, since certain tileforms can be drawn in two different ways.  Consider the tetrahedral die, which, like a tile surface, has 4 vertices:

Two of the vertices are “low” and the other two are “high”, and as a result of this, there are two possible ways to draw the two triangles that connect the four corner vertices.  In the “valley” configuration, the shared edge that connects the two triangles runs between the low vertices (left to right on the image above).  In the “ridge” configuration, the shared edge runs between the two high vertices (top to bottom in this image).  So, from 4 vertices we can get 2 different surface configurations.

I haven’t done the analysis on this to figure out how many of these dual-surface cases there are in the 175 legal tileforms, but I imagine the number’s close to 100.  Tonight I’ll isolate the exact conditions and survey the tileforms to find the number. Once I know, I’ll have to decide whether there’s a clean way to guarantee “all ridges” or “all valleys” in my tileform set, or whether it is advantageous to permit both conformations.

Unlike in my Java implementation, I’m not starting from the assumption of having a predefined set of tiles and then interpolating vertex information from their relative heights, then figuring out which one of my tileforms to shim into that spot.  Instead, I’m going to treat every corner where 4 tiles meet as a distinct puzzle, and search to find out whether there’s a vertex location for each of the 4 vertices that share that corner that lets them make a legal tileform.  If there’s not, I’ll likely aim for the greatest number of solutions, and assume that any unsolvable vertices will be force a “cliff” into the terrain.  This is desirable, because I want cliffs to form.

Tileforms will take up (11 * 22 * 175) * 4 bytes = 165.42Kb of VRAM if I want to store them all as Vertex Buffer Objects.  (3 position, 3 normal, 3 color, 2 texture floats for each of the 22 vertices in a tileform).  More if I include both ridges and valleys. That’s not a completely insignificant amount, and more than my previous implementation, but it might be worth the tradeoff for improved surface quality and the boost in FPS.

Once I have some visuals to compare to previous renders I’ll have to get serious about deciding whether to continue down this road, to make simplifications in the range of possible surface geometries, or to press forward.

**** UPDATE: There are 38 “saddle” cases, where either a valley or a ridge could be employed.  That’s not as many as I feared.  Will have to think about how to choose between the two cases.

Here’s how you bootstrap an LWJGL Display in Scala:

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

class GameDisplay {

  def main(args: Array[String]) {
    val gameDisplay : GameDisplay = new GameDisplay();
	  gameDisplay.start();
  }

   def start() {
    try {
      Display.setDisplayMode(new DisplayMode(800,600));
      Display.create();
    } catch {
        case e: LWJGLException => {
          e.printStackTrace();
          System.exit(0);
        }
    }

	// init OpenGL here
	while (!Display.isCloseRequested()) {
	    // render OpenGL here
	    Display.update();
	}
		
	Display.destroy();
  }
}

Don’t forget to add the native libraries for LWJGL to your VM classpath.

That’s it!  Once you have this, the rest is standard rendering and update loop business.  Have fun!

Eye candy for Isographer, Isometric, Java, OpenGL, Rendering, Terrain, LWJGL,

After a great deal of refactoring, and building a unit testing harness with JUnit for the project, I finally implemented “diagonal hilltop smoothing”. This feature makes a second pass over the terrain and identifies locations that could benefit from a slope inversion. So, instead of “down and then over”, the hilltop pieces move “over and then down” in a diagonal direction. As you can see, this gets rid of a lot of the unnatural “scaling”, where there are diagonal rows of rounded corner bottoms on interior curves of the terrain.

Not all cliff edges are meant to go away. If that were the goal, I’d just make a surface mesh. I want impassable cliff-like edges between certain tiles. Next I’m going to work on improving the appearance of certain impassable ridges. Should be faster, with the new testing tools and refactored terrain processing code.

Eye candy for 3D, Heightmap, LWJGL, Tile, Rendering, Java, OpenGL, Isographer, Isometric,

First iteration of my terrain smoother for Isographer.  Renders a heightmap using a set of 37 discrete tile types.  Hills along cardinal directions, and rounded corners between them.  In the next few days I’ll work on improving the smoother algorithm to account for “scaling” surface texture on the inside of hill curves, and also detecting undergirding cases to prevent gaps in the map under steep terrain.

2nd demonstration of Isographer terrain heightmap processing.  This demo uses the same terrain as the first demo, only with 65x65 datapoints gotten by expansion and diamond square recursion.  Next steps will include discretizing the terrain height values, using the terrain slope data to swap out different angled tile types for smooth surface translation.

First demo of terrain heightmap processing and bare-bones Isometric 3D rendering in Isographer.

Eye candy for Isometric, Terrain, Rendering, Engine, OpenGL, LWJGL,

Early screenshots from Isographer, my new 3D isometric game terrain rendering library.  Written in Java using the lightweight Java games library (LWJGL)

For the last five or six days I’ve been working very diligently to build a simple 3D isometric terrain rendering engine using JavaMonkeyEngine 3 (JME3).  I was delighted when, by the end of the first night I had assembled the necessary geometries for my surface tiles and textured them.  By the end of the second night I’d forced the camera into a parallel projection mode and positioned the scene with respect to the camera so that everything was rendered at the requisite angle.

From this point on, all I really need is to light the scene.  However, this disarmingly simple thing has turned into a frustrating and heart-wrenching struggle.  I have to conclude, after several days of hammering, chiseling, scraping, poring through source code, online tutorials, and relevant books on the subject, that JME3 is not going to work for my project.

After several confusing false starts, I decided to simplify the lighting problem.  I ditched my complex models and implemented a Mesh that is, functionally, half of a cube.  Just the 3 quads that face the camera.  I computed the surface normals for these quads (this is very simple, because the object is a cube, and it is aligned to the X,Y, and Z axes), and then set up three directional spotlights.  One red, one green, and one blue.  The goal of my experiment was to shine each of these lights on a different face of the mesh.  For comparison, I included a com.jme3.scene.shape.Box in the same node as my mesh.

One of the first things that I noticed was that the Box model had to be rotated slightly in order for its normal vectors to align properly—in other words, shining a light straight down from above (0,1,0) lit one of the back-facing quads on the cube.  That was straightforward to resolve by rotating the cube. Still, as the days marched on, no combination of effort and experimentation would result in the effect that I was attempting to produce, which was a light shining down on the tile nearly vertically, while lighting the two vertical faces of the tile at noticeably different degrees. Eventually, I realized that even if I did manage to tweak and torque the normals around and the lights to get the effect I was after, it would almost certainly break or behave counterintuitively once I began to vary the angle of the top of my tiles, which is the whole point of this work.

I’ve decided to give up JME and work more directly with the Lightweight Java Games Library (LWJGL). I don’t need physics, collision detection, particle systems from a massive game engine. I just need rendering. Triangles, textures, and light. Still, it’s a big surprise, to come so frustratingly close to completion only to turn away from an otherwise excellent library.