Showing posts with label game development. Show all posts
Showing posts with label game development. Show all posts

Wednesday, May 13, 2015

Java Landscape Generator - Part 1


Java is one of those languages that I have know about for a long time but never really had anything to do with. Recently though I started to look at the JME3 game engine which is Java and that prompted me to start this little project.

Landscape Generator

What I am considering here goes beyond just a tool to simply create a random heightmap, but for this part I am just going to cover the creation of a heightmap which will be the basis for the landscape and in future posts I will add features and functions aimed at creating the required data to (eventually) generate a game map using the data we output here and a set of premade models for vegetation and buildings, etc.

We will end up with a heightmap that looks something like this.

Random Heightmap after perturbation, erosion and smoothing.


So, let's get started. Fire up your java code editor or ide and start a new project.

( I am using Eclipse Luna )

Noise.

In order to generate a heightmap, we need some sort of noise generation algorithm. The most common one when searching for this sort of thing is Perlin noise. I did some investigation and considered writing a Perlin noise class but in the end I decided to use the excellent OpenSimplexNoise class by Kurt Spencer

Click the link to get that class and import it into your project.

Note: I am not going to cover the basic java conventions such as package names and so on. I will leave that up to you.

Create a class. I named my class NoiseHeightmap because I intend to visit at least one other method of generating a heightmap in future posts.

public class NoiseHeightmap {

Then setup some variables we will use through the generation of the heightmap.

private long Seed = 0;
private int Size = 512;
private static double featureSize = 100; // for OpenSimplexNoise
private double[][] Heightmap;
private OpenSimplexNoise noise;

I setup the constructor to allow us to provide the size of the heightmap as well as a Seed value. Using a specified seed value would allow us to regenerate that same heightmap again if it was required.

public NoiseHeightmap(int size, long seed)
{
     Size = size;
     Seed = seed;
     // Init height map array
     Heightmap = new double[Size][Size];
       
     // Init OpenSimplexNoise
     noise = new OpenSimplexNoise(Seed);

We set the provided size and seed. Then we initialize the multidimensional array to store the height of each point of the height map. We also create a new instance of the OpenSimplexNoise class to provide the noise we will use.

Then proceed to generate the initial basic heightmap. To do this we need to loop through the Size twice. We need to populate every pixel of an image that is (Size * Size) (e.g. 512 pixels x 512 pixels).

// Generate base height map from noise
for( int y = 0; y < Size; y++ )
{
     for( int x = 0; x < Size; x++ )
     {
          Heightmap[x][y] = noise.eval(x / featureSize, y / featureSize, 0.0);
      }
 }

At that point, we have a heightmap. We are dividing by the featureSize value in order to lower the frequency of the noise, meaning we get more rolling hills rather than high frequency noise.
OpenSimplexNoise outputs noise as double from -1 to 1. Which means we have an array of values that could be anywhere between -1 and 1. This isn't really much good if we wanted to output it to an image or use in game. If we saved the data as an image now we'd get something like this.
Our heightmap prior to being normalized.
The same heightmap after the normalization was done.

In order to make it resemble a normal heightmap and make it useful we will need to normalize the data. I will show the function to normalize the data soon but before we do that we want to add some more features to make the heightmap more realistic.

Perturb

This step displaces the height elements according to another noise map with a much higher frequency. This makes the map more rough and lessens the smooth rolling hills a bit. Making the "mountains" a bit more random.

private void Perturb(double f, double d)
    {
        int u, v;
        double temp[][] = new double[Size][Size];
       
        for( int i = 0; i < Size; ++i )
        {
            for( int j = 0; j < Size; ++j)
            {
                u = i + (int)(noise.eval(f * i / (double)Size, f * j / (double)Size, 0) * d);
                v = j + (int)(noise.eval(f * i / (double)Size, f * j / (double)Size, 1) * d);
                if (u < 0) { u = 0; }
                if (u >= Size) { u = Size - 1; }
                if (v < 0) { v = 0; }
                if (v >= Size) { v = Size - 1; }
                temp[i][j] = Heightmap[u][v];
            }
        }
        Heightmap = temp;
    }



Lets look at the same heightmap again after we run the Perturb(16,16) function. Providing different arguments will provide different effects and so you can adjust to suit your project.

After running Perturb(16,16) and normalizing.


Next up we will do some very simple erosion on the heightmap. What this function does is go through every elements Moore neighbourhood (excluding itself) and look for the lowest point, the match. If the difference between the element and its match is between 0 and a smoothness factor, some of the height will be transferred.

The following function needs to be called multiple times to increase the effect.

private void Erode(double smoothness)
    {
        for( int i = 1; i < Size - 1; i++ )
        {
            for( int j = 1; j < Size - 1; j++)
            {
                double d_max = 0.0f;
                int match[] = { 0, 0 };

                for (int u = -1; u <= 1; u++)
                {
                    for (int v = -1; v <= 1; v++)
                    {
                        if(Math.abs(u) + Math.abs(v) > 0)
                        {
                            double d_i = Heightmap[i][j] - Heightmap[i + u][j + v];
                            if (d_i > d_max)
                            {
                                d_max = d_i;
                                match[0] = u;
                                match[1] = v;
                            }
                        }
                    }
                }

                if(0 < d_max && d_max <= (smoothness / (double)Size))
                {
                    double d_h = 0.5f * d_max;
                    Heightmap[i][j] -= d_h;
                    Heightmap[i + match[0]][j + match[1]] += d_h;
                }
            }
        }
    }

Lets have a look at that same heightmap again after 50 passes of Erode(18).

NOTE: 50 passes of Erode(18) could very well be too strong to be much use, I did it for this to clearly see the difference it make on the heightmap.

Same heightmap after 50 x Erode(18)





You should be able to see the layers created on the steeper sections. As I said above, 50 passes of Erode(18) might be too much. Adjust according to need. Keeping in mind that our last step (before normalizing) is to smooth it out a bit.


private void Smoothen()
    {
        for( int i = 1; i < Size - 1; ++i )
        {
            for( int j = 1; j < Size - 1; ++j)
            {
                double total = 0.0;

                for (int u = -1; u <= 1; u++)
                {
                    for (int v = -1; v <= 1; v++)
                    {
                        total += Heightmap[i + u][j + v];
                    }
                }

                Heightmap[i][j] = total / 9.0;
            }
        }
    }

We call Smoothen once after we're finished doing the erosion in order to smooth it out a little.

This is what it looks like after being smoothed. (because our erosion was strong the smoothing is only slight, almost like a soft blur)

After smoothing

Finally we get to the normalizing.

The reason we leave normalizing to the end is that it is possible (using non-normalized data) to create multiple heightmaps that join together into a seamless and much larger terrain. I won't cover that here, but maybe will touch on it later in the series of tutorials. But to output the heightmap to a usable image at last we first need to normalize it.

Here is the normalize function.


private void Normalize()
    {
        for( int y = 0; y < Size; y++ )
        {
            for( int x = 0; x < Size; x++ )
            {
                Heightmap[x][y] = (Heightmap[x][y] - -1) / (1 - -1);
            }
        }
    }

 We're basically looping through each point and converting it's height to an equivalent height within the range of 0 - 1

New height = (current_height - min height) / (max height - min height)

I already mentioned the OpenSimplexNoise we're using produces values between -1 and 1, so the conversion seen above should normalize that back to 0-1.

By the end, the constructor function should look something like this.


public NoiseHeightmap(int size, long seed)
    {
        Size = size;
        Seed = seed;
       
        // Init height map array
        Heightmap = new double[Size][Size];
       
        // Init OpenSimplexNoise
        noise = new OpenSimplexNoise(Seed);
       
        // Generate base height map from noise
        for( int y = 0; y < Size; y++ )
        {
            for( int x = 0; x < Size; x++ )
            {
                Heightmap[x][y] = noise.eval(x / featureSize, y / featureSize, 0.0);
            }
        }
               
        // Perturb
        Perturb(16,16);
               
        // Erode
        for( int i = 0; i < 50; i++ )
        {
            Erode(18.0);
        }
       
        // Smoothen
        Smoothen();
       
        // Normalise
        Normalise();
       
    }
I also had a LandscapeGenerator Class which has the main() function and within that I call the NoiseHeightmap class to generate the heightmap.

To output the heightmap to a PNG image for use in a rendering engine you can do so with the function like this. (NOTE: Ensure you normalize the heightmap before saving it as an image.

public void HeightmapPNG(double[][] data, String filename) throws IOException
    {
        int size = data[0].length;
      
        BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
      
        for (int y = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++)
            {
              
                double value = data[x][y];
                int rgb = 0x010101 * (int)(255 * value);
                image.setRGB(x, y, rgb);
              
            }
        }
      
        ImageIO.write(image, "png", new File(filename));
      
    }

I've implemented this in a seperate Output class which will be extended upon to provide functions to output the other data we will generate in the next parts of the tutorial.

In Part 2 I will look at creating a "steepness" mask which will provide us with an easy way to determine flat areas for vegetation and buildings. I will also look at generating a "splat-map" for the JMonkeyEngine terrain texture splatting. We'll also look at what the terrain looks like when rendered using the heightmap and texture splat map.

In Part 3 I intend to look at the generation of rivers and maybe lakes as well as roads.

Stay tuned for more coming soon.

(The algorithms here are converted to Java by myself and based on the c++ turorial from float4x4.net)

Monday, June 16, 2014

Code a Simple Day/Night Cycle Part 1


Not all games require a day night cycle system and plenty would not benefit from having one, but it is a staple requirement of adventure and role playing games. It seems like a complex thing at first consideration but at least for a basic system it is not really that hard. When you start with a basic system like this, it becomes easier then to customise and add features to make your game stand out.

My aim in this tutorial is to look at the code required, but in order to do so I will also need to touch on the setup of the elements in the game engine to make this happen. I will assume you have at least some knowledge of coding and the engine you plan to use so I am going to use pseudo-code here. I will leave it to you to adapt it to the language and engine of your choice.

Setting up the sun


Before we get into the code, the very simple day night cycle here will require a little setup in your game engine. Some (but not all) of the game engines I have experimented with seem to allow you to simply rotate a directional light, and they handle the billboarding of the sun moving through the sky. In some others, or if coding one from scratch, you may need to consider a setup such as this.

Create an invisible scene node as the sunController and position it at approx the center of your map.
Create a sceneNode as a child of the sunController and position it a good distance away from the sunController, attach the sun directional light to this node and set its direction to be facing the sunController. I will leave you to work out billboards, etc.

Now, as you rotate the sunController around the desired axis, the sun light will move around as well. You should be able to preview the effect and fine tune it by manually setting the rotation of the sunController and viewing what it does with the light.


Code the sun

Now, lets get to the bit you're really interested in. Psuedo-code. Well, actually, you'd probably prefer real code, but then you would just copy and not really learn much.

Lets say we want our data about the time to be available to other parts of our game (such as when saving a game). We will want to create a class that can be accessed to gather that information. This could be useful not just for saving a players game but to provide other dynamic features such as moving clocks or events that are triggered at certain times.

class GameTime {
.....
}

Now, we will need to have some variables to store the time information we want.

public float currentHour;       // The current hour of the game day
public int currentDay;        // The current day of the week
public int currentWeek;     // The current week of the month
public int currentMonth;    // The current month of the year
public int currentYear;       // The current year

Now for some basic variables that will help control our sun. This includes how long 1 game day will last and uses that to calculate some other required data.

// 1 in game day will take 30 real minutes (30m * 60s = 1800s)
private int dayLengthInSeconds = 1800;

// Calculate how many degrees the sun needs to move through the sky per second
private float sunRotationDegPerSecond = 360f / dayLengthInSeconds;

// How many degrees of sun rotation per game hour (assuming 24 hour days)
float degreesPerHour = 360f / 24;

// current rotation of the sun
private float sunRot = 0;

// GameObject for our sunController (or Sun as required)
public GameObject sunController;

// Constructor
public function GameTime() {

// Set the GameObject
sunController = EngineSpecificGetEntityFunctionCallHere();

// calculate starting position based on time
sunRot = currentHour * degreesPerHour;

// Set the initial rotation around specified axis (z)
// How this is done will vary based on the engine. 
// Again, this is psuedo-code not to be copied verbatim
sunController.rotation.z = sunRot;
}

// The update function will be called every frame
public function Update(float frameDelta) {

// Check if the sun object exists
if(sunController != null) {

// Reset to 0 if require
if(sunRot > 360f) {
sunRot = 0;
currentDay++;
}

// Calculate rest of time
if(currentDay > 7) { //assuming 7 day weeks
currentWeek++;
currentDay = 1;
}

if(currentWeek > 4) { // assuming 4 weeks to a month
currentMonth++;
currentWeek = 1;
}

if(currentMonth > 12) { // assuming 12 months in a year
currentYear++;
currentMonth = 1;
}


// Update the suns rotation by taking the degreesPerSecond and 
// multiplying by the frameDelta to calculate the degrees for 
// that part of that second.
sunRot += degreesPerSecond * frameDelta;

// Set the rotation around specified axis (z)
// How this is done will vary based on the engine. 
// Again, this is psuedo-code not to be copied verbatim 
sunController.rotation.z = sunRot;

// Recalculate currentHour

currentHour = sunRot / degreesPerHour;

}

}


Basically what we are doing here is determining how long we want an in game day to last in real time and then calculating how much to move the sun so that it does a full 360 degree rotation within that time period.

30 minutes might be too short or too long for your game, it is completely configurable. You could set it to quite a short period in order to test the functionality quickly. However, if you intent to lower the number of seconds a day lasts below 360 you will likely need to make some adjustments in the code to compensate for that.

By taking the number of degrees we need to rotate per second and multiplying that by our frameDelta we get the correct degrees of rotation for that frame which is generally a fraction of a second.

In the constructor we set the initial rotation based on the currentHours, so this could be preset to 6 to have the sun start at sunrise, 18 for sundown, 12 for midday, etc.

It may take some tweaking to get correct, but with a bit of experimentation you should have a sun tracking through the sky and giving you 12 hours of sunlight and 12 hours of darkness.

Good work.

This is only part 1 of the overall day/night cycle solution however. In part 2 we will look at the blending of a skybox or skydome to allow our map to have a black sky at night, a bright orange glow at sunrise/sunset and a sky blue during the day.

Stay tuned for part 2 coming soon.

P.S. If you would really like to see code to suit Unity 3d or NeoAxis please comment below and I will consider providing it. Although the implementation might be slightly different to suit the specifics of those Engines.

I'm back


My apologies for the extended absence from here. I'm back now and working on some new stuff to publish. I'm working on some tutorials to do with :

  • coding day/night cycles with game engines such as Unity or Neoaxis
  • some Arduino stuff to control some devices (I will provide hardware specs and code)
  • Some more PHP tutorials
  • Some photography tips, tricks and tutorials
  • Linux vs Windows for common tasks tutorials
  • Some hardware reviews
  • and whatever else I can think of.
I hope you will tune in and enjoy my future content. I appreciate the support.