Anyone see the bug in this terrain generation code?

This is some code that uses a fractal algorithm to generate a 1D line for a 2D game. I thought it was OK until I tried changing the map size, when I realized that the terrain actually stretches with the map. A 2048x128 map looks like a flat plain with gently rolling hills; a 256x256 one with the same settings is extremely jagged and almost impossible to traverse.

Changing the factor in the bottom of LineSegment.java is a workaround (this determines how much the random number range decreases in each iteration), but I don’t want to do it that way–that number should reflect the roughness of the terrain, not the size of the map!

public class LineSegment {
    public double x1;
    public double y1;
    public double x2;
    public double y2;
    public int level;
    private double range;
    public LineSegment(double x1, double y1, double x2, double y2, int level, double roughness)
    {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        this.level = level;
        this.range = roughness;
    }
    public LineSegment[] subdivide(Random r)
    {
        double midpoint = (x1 + x2)/2;
        LineSegment[] t = new LineSegment[2];
        double rand;

        if(level == 1)
        {
            double upperLimit = 0.20;
            double lowerLimit = -0.20;
            double randomRange = upperLimit - lowerLimit;
            rand = lowerLimit + r.nextDouble() * randomRange;
        }
        else
        {
            rand = ((y1+y2)/2) + (r.nextDouble() * (range * 2) - range);
        }
        double l1x1 = x1;
        double l1y1 = y1;
        double l1x2 = midpoint;
        double l1y2 = rand;
        double l2x1 = midpoint;
        double l2y1 = rand;
        double l2x2 = x2;
        double l2y2 = y2;
        double factor = 0.620;
        LineSegment l1 = new LineSegment(l1x1, l1y1, l1x2, l1y2, level + 1, range * factor);
        LineSegment l2 = new LineSegment(l2x1, l2y1, l2x2, l2y2, level + 1, range * factor);
        t[0] = l1;
        t[1] = l2;
        return t;
    }
}
    public void generate()
    {
        Random r = new Random();
        int numLineSegments = width;
        Vector<LineSegment> result = new Vector<LineSegment>(numLineSegments);
        double upperLimit = 0.20;
        double lowerLimit = -0.20;
        double range = upperLimit - lowerLimit;
        result.add(new LineSegment(0, lowerLimit + r.nextDouble() * range, width, lowerLimit + r.nextDouble() * range, 1, 1));
        int currentLevel = 1;
        while(result.size() < numLineSegments)
        {
            Iterator<LineSegment> itr = result.iterator();
            Vector<LineSegment> tmp = new Vector<LineSegment>(numLineSegments);
            while(itr.hasNext())
            {
                LineSegment l = itr.next();
                if(l.level == currentLevel)
                {
                    LineSegment[] parts = l.subdivide(r);
                    itr.remove();
                    tmp.add(parts[0]);
                    tmp.add(parts[1]);
                }
            }
            result.clear();
            Iterator<LineSegment> tmpItr = tmp.iterator();
            while(tmpItr.hasNext())
            {
                result.add(tmpItr.next());
            }
            currentLevel++;
        }

I’m 99% sure that rendering code isn’t the problem, but just in case:

        for(Object o : result.toArray())
        {
            LineSegment l = (LineSegment) o;
            int blockX = (int) l.x1;
            int blockY = (int) (height * ((l.y1 + 1) / 2)); //It is originally a number from -1 to 1
            if(blockX >= 0 && blockX < width && blockY >= 0 && blockY < width)
            {
                setBlock(blockX, blockY, 3); //Grass
                int columnY = blockY - 1;
                for(int y = 0; y < 5; y++) //Dirt
                {
                    if(columnY > 0)
                        setBlock(blockX, columnY, 2);
                    columnY--;
                }
                while(columnY > 0) //Stone
                {
                    setBlock(blockX, columnY, 1);
                    columnY--;
                }
            }
        }

I’d guess it’s this line in generate():

double range = upperLimit - lowerLimit;

Shouldn’t the initial range depend on the length of the line? Perhaps something like:

double range = ((upperLimit - lowerLimit)*width)/2048;

so that the range works the same for an initial line of length 2048, but uses a lower range for lines length 256.

That’s not the problem, range is only used to determine how much the midpoint of the very first line segment is displaced.

I think the factor in subdivide() needs to be calculated based on the width of the level somehow, but I’m not sure.

        double factor = 0.620;
        LineSegment l1 = new LineSegment(l1x1, l1y1, l1x2, l1y2, level + 1, range * factor);
        LineSegment l2 = new LineSegment(l2x1, l2y1, l2x2, l2y2, level + 1, range * factor);