Hey gang,
I just thought I'd post this since I just verified that it works for all three (yep all three) development environments. The DBP/DBC code is identical. The DGDK code is nearly identical. To save space, I'll include all of the code in a single post.
The technique is based on the Midpoint Displacement Algorithm that I learned back when I was in college working on transputers. Anyway, that isn't important. What is important is that you can make some pretty cool heightmaps for your terrains with very little effort.
You may have to run the app a few times to truly see the versatility of this technique, but I'm sure that someone will find it useful. Feel free to use it if it helps!
-Frank
Here's the screenshots:
And here's the code:
[DBP/DBC code]
hide mouse
autocam off
sync on
sync rate 30
` initialize the backbuffer
sync
`-------------------------------------------------------------------
` For the terrain generation to work properly, size must be a power
` of 2 this keeps the process clean and easy to understand, but with
` a bit of work you can easily adapt it to use any size. I'm just
` partial to powers of 2.
`-------------------------------------------------------------------
size = 256
nSize = size - 1
` where our terrain height information is stored
dim terrain(size, size)
dim strings$(3)
strings$(0) = "2D Heightmap";
strings$(1) = "Press a key when ready";
strings$(2) = "Heightmap used to define a matrix";
strings$(3) = "Press Esc (or F12 in DBC) to exit";
randomize timer()
` this call is all you need
initTerrain(size)
heightmapShowcase(size, nSize)
displayPrompt(0, 1)
sync
wait key
matrixShowcase(size)
do
displayPrompt(2, 3)
sync
loop
end
function displayPrompt(str1, str2)
ink rgb(32, 32, 32), 0
box 0, 0, 640, 40
ink rgb(0, 255, 0), 0
center text 320, 0, strings$(str1)
center text 320, 20, strings$(str2)
endfunction
`-------------------------------------------------------------------
` showcase our terrain heightmap as a 2D bitmap
`-------------------------------------------------------------------
function heightmapShowcase(size, nSize)
` these offsets center our heightmap on screen (in 640x480)
xOffset = 320 - (size / 2)
yOffset = 240 - (size / 2)
` display the heightmap
for y = 0 to nSize
for x = 0 to nSize
c = terrain(x, y)
ink rgb(c, c, c), 0
dot x + xOffset, y + yOffset
next x
next y
endfunction
`-------------------------------------------------------------------
` showcase our terrain heightmap as a 3D matrix
`-------------------------------------------------------------------
function matrixShowcase(size)
` constrains our size to a reasonable size for our matrix display
if (size > 64) then size = 64
nSize = size - 1
` make our matrix -- note that I'm using nSize instead of size for
` the number of segments. The heightmap defines the height of the
` corners of each tile, not the height of the tile as a whole.
make matrix 1, size * 10, size * 10, nSize, nSize
` set the heights
for z = 0 to nSize
for x = 0 to nSize
set matrix height 1, x, z, terrain(x, z)
next x
next z
` update our matrix
update matrix 1
` color the backdrop black because the blue is really bright
color backdrop 0
position camera size * 5, 200, 0
point camera size * 5, 150, 150
endfunction
`-------------------------------------------------------------------
` This function performs the setup for the terrain generation
` function and is the function that we call
`-------------------------------------------------------------------
function initTerrain(size)
`----------------------------------------------------------------
` choose random heights for the 4 corners
`----------------------------------------------------------------
c1 = rnd(size - 1)
c2 = rnd(size - 1)
c3 = rnd(size - 1)
c4 = rnd(size - 1)
`----------------------------------------------------------------
` now set the heights for the 4 corners
`----------------------------------------------------------------
terrain(0, 0) = c1
terrain(0, size - 1) = c2
terrain(size - 1, 0) = c3
terrain(size - 1, size - 1) = c4
`----------------------------------------------------------------
` call our recursive terrain generation function
`----------------------------------------------------------------
divide(0, 0, size, c1, c2, c3, c4);
endfunction
`-------------------------------------------------------------------
` Returns a random value between min and max regardless of which is
` actually larger.
`-------------------------------------------------------------------
function rangedRandom(minVal, maxVal)
if (maxVal >= minVal)
result = rnd(maxVal - minVal) + minVal
else
result = rnd(minVal - maxVal) + maxVal
endif
endfunction result
`-------------------------------------------------------------------
` This is the magic function that creates the terrain. For a more
` in-depth explanation of this technique, Google "Midpoint
` Displacement Algorithm."
`-------------------------------------------------------------------
function divide(x, y, size, c1, c2, c3, c4)
` cut our current working area in half in both directions (makes 4 pieces)
newSize = size / 2
` this is a random adjustment to make our terrain more natural
` Comment this out to see the difference it has on the result. Also
` by changing the range, you can change how smooth the terrain is.
displacement = rangedRandom((-4 * size), (4 * size))
if (size > 1)
` average the points along each edge to get the midpoints on the edges
edge1 = (c1 + c2) / 2;
edge2 = (c2 + c3) / 2;
edge3 = (c3 + c4) / 2;
edge4 = (c4 + c1) / 2;
` average the 4 corners to get our center, plus modify it a bit with our random displacement
center = ((c1 + c2 + c3 + c4) / 4) + displacement;
` make sure that our random addition doesn't send us outside our acceptable range of 0-255
if (center > 255)
center = 255;
else
if (center < 0)
center = 0;
endif
endif
` repeat for the four smaller grids we've defined with our new points
divide(x, y, newSize, c1, edge1, center, edge4);
divide(x + newSize, y, newSize, edge1, c2, edge2, center);
divide(x, y + newSize, newSize, edge4, center, edge3, c4);
divide(x + newSize, y + newSize, newSize, center, edge2, c3, edge3);
` this is our "stop case" where we're down to a single unit in the terrain grid
else
color = (c1 + c2 + c3 + c4) / 4;
terrain(x, y) = color;
endif
endfunction
[Dark GDK code]
#include "DarkGDK.h"
//-----------------------------------------------------------------------------
// For the terrain generation to work properly, size must be a power
// of 2 this keeps the process clean and easy to understand, but with
// a bit of work you can easily adapt it to use any size. I'm just
// partial to powers of 2.
//-----------------------------------------------------------------------------
const int g_size = 256;
// this is just a convenience for displaying our prompts
char *strings[4] = {"2D Heightmap",
"Press a key when ready",
"Heightmap used to define a matrix",
"Press Esc (or F12 in DBC) to exit"};
// where our terrain height information is stored
int g_terrain[g_size][g_size];
// function prototypes
void initTerrain(void);
void divide(int x, int y, int size, int c1, int c2, int c3, int c4);
int rangeRandom(int min, int max);
void displayPrompt(int str1, int str2);
void heightmapShowcase(void);
void matrixShowcase(void);
/******************************************************************************
* the main entry point for the application is this function
******************************************************************************/
void DarkGDK(void) {
int nSize;
// get rid of our mouse cursor
dbHideMouse();
// turn on sync rate and set maximum rate to 60 fps
dbSyncOn();
dbSyncRate(30);
dbAutoCamOff();
// go into fullscreen mode
//dbSetWindowOff(); // this method crashes on my laptop in this example and a few others
dbSetWindowLayout(0, 0, 0);
dbMaximizeWindow();
// initialize the backbuffer
dbSync();
dbRandomize(dbTimer());
// this call is all you need to generate the heightmap
initTerrain();
heightmapShowcase();
displayPrompt(0, 1);
dbWaitKey();
matrixShowcase();
// our main loop
while (LoopGDK()) {
// display our prompt until the user ends the program
displayPrompt(2, 3);
// update the screen
dbSync();
}
// return back to windows
return;
}
//-----------------------------------------------------------------------------
// Sets the 4 corners of the terrain at random, then calls the recursive
// function that will build the rest of the terrain.
//-----------------------------------------------------------------------------
void initTerrain(void) {
// randomly choose values for the four corners
int c1 = dbRnd(255);
int c2 = dbRnd(255);
int c3 = dbRnd(255);
int c4 = dbRnd(255);
g_terrain[0][0] = c1;
g_terrain[g_size-1][0] = c2;
g_terrain[0][g_size-1] = c3;
g_terrain[g_size-1][g_size-1] = c4;
// make our terrain based on these four corners
divide(0, 0, g_size, c1, c2, c3, c4);
}
//-------------------------------------------------------------------
// Returns a random value between min and max regardless of which is
// actually larger.
//-------------------------------------------------------------------
int rangeRandom(int min, int max) {
int result;
if (max >= min) {
result = dbRnd(max - min) + min;
} else {
result = dbRnd(min - max) + max;
}
return result;
}
//-------------------------------------------------------------------
// This is the magic function that creates the terrain. For a more
// in-depth explanation of this technique, Google "Midpoint
// Displacement Algorithm."
//-------------------------------------------------------------------
void divide(int x, int y, int size, int c1, int c2, int c3, int c4) {
int color;
int edge1, edge2, edge3, edge4, center;
// When we cut the current piece into quarters, we divide our size
// by two. This value represents the length of an edge as opposed to
// the area of the new piece
int newSize = size / 2;
int displacement = rangeRandom(-4 * size, 4 * size); // generate a random number in the desired range
if (size > 1) {
// average the points along each edge to get the midpoints on the edges
edge1 = (c1 + c2) / 2;
edge2 = (c2 + c3) / 2;
edge3 = (c3 + c4) / 2;
edge4 = (c4 + c1) / 2;
// average the 4 corners to get our center, plus modify it a bit with our random displacement
center = ((c1 + c2 + c3 + c4) / 4) + displacement;
// make sure that our random addition doesn't send us outside our acceptable range (0-255)
if (center > 255) {
center = 255;
} else if (center < 0) {
center = 0;
}
// repeat for the four smaller grids we've defined with our new points
divide(x, y, newSize, c1, edge1, center, edge4);
divide(x + newSize, y, newSize, edge1, c2, edge2, center);
divide(x, y + newSize, newSize, edge4, center, edge3, c4);
divide(x + newSize, y + newSize, newSize, center, edge2, c3, edge3);
}
else { // this is our "stop case" where we're down to a single unit in the terrain grid
color = (c1 + c2 + c3 + c4) / 4;
g_terrain[x][y] = color;
}
}
//-------------------------------------------------------------------
// display our prompts for the example
//-------------------------------------------------------------------
void displayPrompt(int str1, int str2) {
dbInk(dbRGB(32, 32, 32), 0);
dbBox(0, 0, 640, 40);
dbInk(dbRGB(0, 255, 0), 0);
dbCenterText(320, 0, strings[str1]);
dbCenterText(320, 20, strings[str2]);
}
//-------------------------------------------------------------------
// showcase our terrain heightmap as a 2D bitmap
//-------------------------------------------------------------------
void heightmapShowcase() {
int xOffset, yOffset, x, y, c;
// these offsets center our heightmap on screen (in 640x480)
xOffset = 320 - (g_size / 2);
yOffset = 240 - (g_size / 2);
// display the heightmap
for (y = 0; y < g_size; y++) {
for (x = 0; x < g_size; x++) {
c = g_terrain[x][y];
dbInk(dbRGB(c, c, c), 0);
dbDot(x + xOffset, y + yOffset);
}
}
}
//-------------------------------------------------------------------
// showcase our terrain heightmap as a 3D matrix
//-------------------------------------------------------------------
void matrixShowcase() {
int size, x, z;
// constrains our size to a reasonable size for our matrix display
if (g_size > 64) {
size = 64;
} else {
size = g_size;
}
// make our matrix -- note that I'm using (size - 1) instead of size
// for the number of segments. The heightmap defines the height of
// the corners of each tile, not the height of the tile as a whole.
dbMakeMatrix(1, size * 10, size * 10, size - 1, size - 1);
// set the heights
for (z = 0; z < size; z++) {
for (x = 0; x < size; x++) {
dbSetMatrixHeight(1, x, z, g_terrain[x][z]);
}
}
// update our matrix
dbUpdateMatrix(1);
// color the backdrop black because the blue is really bright
dbColorBackdrop(0);
dbPositionCamera(size * 5, 200, 0);
dbPointCamera(size * 5, 150, 150);
}