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: http://i.imgur.com/a7zaVFa.png

Any advice or articles that help?

\$\endgroup\$
  • 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 '16 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\$ – zebleckDAMM Aug 24 '16 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 '16 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\$ – zebleckDAMM Aug 24 '16 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 '16 at 15:05
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
    \$\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 '16 at 13:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.