2
\$\begingroup\$

I recently tried reading about procedurally generating and I found out that perlin noise is probably a good way to do it.

Now one way of generating 2D terrain would be to have 2D perlin noise and depending on the height of the noise at a certain point place a certain tile. My question is now, how could I modify the perlin noise so that it generates a planet? It would probably need to be a circle, in the middle of which the noise is high, while outside of the circle its pretty low. The overall shape of the noise would be rectangular.

In the end it should look something like this, but noisy: https://i.sstatic.net/QqFWH.png

Any advice or articles that help?

\$\endgroup\$
17
  • 2
    \$\begingroup\$ "2d tile planet" is an oxymoron. Mapping a 3d sphere to a 2d plane is far more difficult than it seems. With square tiles it's impossible. When you want to use noise to create a 2d picture of a planet (for a background image, for example), the noise needs to have a higher density the closer you are to the edge. \$\endgroup\$
    – Philipp
    Aug 24, 2016 at 14:46
  • \$\begingroup\$ @Philipp why would i need to map a 3d sphere to a 2d plane? You know Terraria? I mean, generate terrain like that (they do it with perlin noise too), just in a circle form. \$\endgroup\$ Aug 24, 2016 at 14:59
  • 2
    \$\begingroup\$ @zebleckDAMM Terraria isn't a circle, it's a rectangle. And if it's tiled and 2D you can't get a circle shape, how would you do the angles? \$\endgroup\$
    – DH.
    Aug 24, 2016 at 15:01
  • \$\begingroup\$ @DH. I think I expressed my idea wrong, I mean it should be a rectangle, but in the middle of the noise should have many high points while outside of the circle, it should have only low points, you know what I mean? \$\endgroup\$ Aug 24, 2016 at 15:04
  • \$\begingroup\$ @zebleckDAMM Please edit your question to make it more clear what exactly you are asking about. When you say "Planet" then people think you mean "Planet", not a 2d orthogonal map which wraps around on the x-axis. \$\endgroup\$
    – Philipp
    Aug 24, 2016 at 15:05

1 Answer 1

3
\$\begingroup\$

Got it now, I wanted to create the planet with perlin noise, but changed to simplex noise, which is similar.

To get the planet shape, I used a radial gradient, but the one used for creating island was too smooth, which wouldnt result in a planet shape, so I created a radial gradient, which ends more abruptly, like this (the black background beyond the circle is actually transparent, I dont know how to make it white).

After that just generate the simplex noise, get the alpha data from the gradient (in my case) and then go through every pixel and subract 1 - (the gradient alpha value from the pixel) from the perlin noise value.

At this point, you already have a noise in the shape of a planet, to get terrain, you can get the noise values from 0 - 1 and compare them: If the value is 0, do nothing, if its less than 0.4, draw dirt, if its less than 1, place stone ( and then some special things to place grass).

After all that, you get this

Heres the improvised code:

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class PlanetGen {
    public static void main(String[] args) throws IOException {

        // GET GRADIENT DATA
        File gradientFile = new File("assets/gradient.png");

        BufferedImage bi=ImageIO.read(gradientFile);
        int[] pixel;

        double highestAlpha = 255;

        double[][] alpha = new double[bi.getWidth()][bi.getHeight()];

        for (int y = 0; y < bi.getHeight(); y++) {
            for (int x = 0; x < bi.getWidth(); x++) {
                pixel = bi.getRaster().getPixel(x, y, new int[4]);

                alpha[x][y] = 1 - (pixel[3] / highestAlpha);
            }
        }

        SimplexNoise simplexNoise=new SimplexNoise(100,0.1,5000);

        // GENERATE SIMPLEX NOISE
        double xStart=0;
        double XEnd=500;
        double yStart=0;
        double yEnd=500;

        int xResolution=bi.getWidth();
        int yResolution=bi.getHeight();

        double[][] result=new double[xResolution][yResolution];

        double lowestNumber = 0;
        double highestNumber = 0;
        for(int i=0;i<xResolution;i++){
            for(int j=0;j<yResolution;j++){

                int x=(int)(xStart+i*((XEnd-xStart)/xResolution));
                int y=(int)(yStart+j*((yEnd-yStart)/yResolution));
                double currentNumber = result[i][j] = 0.5*(1+simplexNoise.getNoise(x,y));

                if(currentNumber < lowestNumber) lowestNumber = currentNumber;
                if(currentNumber > highestNumber) highestNumber = currentNumber;
            }
        }

        // NORMALIZE NOISE DATA
        for(int i=0;i<xResolution;i++){
            for(int j=0;j<yResolution;j++){
                result[i][j] = (result[i][j] - lowestNumber)/(highestNumber - lowestNumber);
            }
        }

        //SUBRACT GRADIENT FROM NOISE
        for(int i=0;i<xResolution;i++){
            for(int j=0;j<yResolution;j++){
                result[i][j] = Math.max(0, result[i][j] - alpha[i][j]);
            }
        }

        // DRAW PLANET IMAGE
        int tileWidth = 8;

        BufferedImage imgDirt = ImageIO.read(new File("assets/dirt.png"));
        BufferedImage imgGrass = ImageIO.read(new File("assets/grass.png"));
        BufferedImage imgStone = ImageIO.read(new File("assets/stone.png"));

        BufferedImage img = new BufferedImage(bi.getWidth()*8, bi.getHeight()*8, BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2 = img.createGraphics();

        for(int x = 0; x < xResolution; x++) {
            for(int y = 0; y < yResolution; y++) {
                double value = result[x][y];

                if(value == 0) {
                    g2.setColor(Color.BLACK);
                    g2.fillRect(x*tileWidth, y*tileWidth, tileWidth, tileWidth);
                } else if(value < 0.4) {
                    boolean isGrass = false;
                    for(int i = 0; i < 3; i++) {
                        for(int j = 0; j < 3; j++) {
                            if(result[x-1+i][y-1+j] == 0) isGrass = true;
                        }
                    }
                    if(isGrass) { 
                        g2.drawImage(imgGrass, x*8, y*tileWidth, tileWidth, tileWidth, null); 
                    } else { 
                        g2.drawImage(imgDirt, x*8, y*tileWidth, tileWidth, tileWidth, null); 
                    }
                } else {
                    g2.drawImage(imgStone, x*8, y*tileWidth, tileWidth, tileWidth, null);
                }
            }
        }

        ImageIO.write(img, "png", new File("./planetwithtexture.png"));

        g2.dispose();
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Ah, now I finally see what you were talking about all along. All of the confusion could have been avoided if you would have posted a mockup which looks like that. \$\endgroup\$
    – Philipp
    Aug 25, 2016 at 13:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .