In principle, you just need to create a single seamless textures, but if you want a large world, it has to be very big.
You can have several seamless textures that you tile instead and make sure that your left world matches the right world tiles.
You can also do this procedurally with different types of periodic noise.
Here is a noise class that I use for smooth noise, both periodic and not:
public class GradientNoise {
final static int TAB_SIZE = 512;
final static int TAB_MASK = TAB_SIZE - 1;
double[] gradientTab = new double[TAB_SIZE * 3];
Random rnd;
double[] valueTab = new double[TAB_SIZE];
public GradientNoise(long seed) {
rnd = new Random(seed);
fillGradientTab();
}
public void setSeed(long seed) {
if (rnd == null) {
rnd = new Random(seed);
} else {
rnd.setSeed(seed);
}
fillGradientTab();
}
private void fillGradientTab() {
for (int i = 0; i < TAB_SIZE; i++) {
double z = getRandom();
double r = Math.sqrt(1.0 - z * z);
double theta = 2.0 * Math.PI * rnd.nextDouble();
gradientTab[3 * i] = r * Math.cos(theta);
gradientTab[3 * i + 1] = r * Math.sin(theta);
gradientTab[3 * i + 2] = z;
}
}
public double noise(double x, double y, double z) {
int ix = MathUtils.floorInt(x);
double fx0 = x - ix;
double fx1 = fx0 - 1.0;
double wx = MathUtils.smoothStep(fx0);
int iy = MathUtils.floorInt(y);
double fy0 = y - iy;
double fy1 = fy0 - 1.0;
double wy = MathUtils.smoothStep(fy0);
int iz = MathUtils.floorInt(z);
double fz0 = z - iz;
double fz1 = fz0 - 1.0;
double wz = MathUtils.smoothStep(fz0);
double vx0 = latticeValue(ix, iy, iz, fx0, fy0, fz0);
double vx1 = latticeValue(ix + 1, iy, iz, fx1, fy0, fz0);
double vy0 = MathUtils.lerp(wx, vx0, vx1);
vx0 = latticeValue(ix, iy + 1, iz, fx0, fy1, fz0);
vx1 = latticeValue(ix + 1, iy + 1, iz, fx1, fy1, fz0);
double vy1 = MathUtils.lerp(wx, vx0, vx1);
double vz0 = MathUtils.lerp(wy, vy0, vy1);
vx0 = latticeValue(ix, iy, iz + 1, fx0, fy0, fz1);
vx1 = latticeValue(ix + 1, iy, iz + 1, fx1, fy0, fz1);
vy0 = MathUtils.lerp(wx, vx0, vx1);
vx0 = latticeValue(ix, iy + 1, iz + 1, fx0, fy1, fz1);
vx1 = latticeValue(ix + 1, iy + 1, iz + 1, fx1, fy1, fz1);
vy1 = MathUtils.lerp(wx, vx0, vx1);
double vz1 = MathUtils.lerp(wy, vy0, vy1);
return 2.0 * MathUtils.lerp(wz, vz0, vz1);
}
public double periodicNoise(double x, double y, double z, int periodX,
int periodY, int periodZ) {
int ix = MathUtils.floorInt(x);
double fx0 = x - ix;
double fx1 = fx0 - 1.0;
double wx = MathUtils.smoothStep(fx0);
int iy = MathUtils.floorInt(y);
double fy0 = y - iy;
double fy1 = fy0 - 1.0;
double wy = MathUtils.smoothStep(fy0);
int iz = MathUtils.floorInt(z);
double fz0 = z - iz;
double fz1 = fz0 - 1.0;
double wz = MathUtils.smoothStep(fz0);
double vx0 = periodicLatticeValue(ix, iy, iz, fx0, fy0, fz0, periodX,
periodY, periodZ);
double vx1 = periodicLatticeValue(ix + 1, iy, iz, fx1, fy0, fz0,
periodX, periodY, periodZ);
double vy0 = MathUtils.lerp(wx, vx0, vx1);
vx0 = periodicLatticeValue(ix, iy + 1, iz, fx0, fy1, fz0, periodX,
periodY, periodZ);
vx1 = periodicLatticeValue(ix + 1, iy + 1, iz, fx1, fy1, fz0, periodX,
periodY, periodZ);
double vy1 = MathUtils.lerp(wx, vx0, vx1);
double vz0 = MathUtils.lerp(wy, vy0, vy1);
vx0 = periodicLatticeValue(ix, iy, iz + 1, fx0, fy0, fz1, periodX,
periodY, periodZ);
vx1 = periodicLatticeValue(ix + 1, iy, iz + 1, fx1, fy0, fz1, periodX,
periodY, periodZ);
vy0 = MathUtils.lerp(wx, vx0, vx1);
vx0 = periodicLatticeValue(ix, iy + 1, iz + 1, fx0, fy1, fz1, periodX,
periodY, periodZ);
vx1 = periodicLatticeValue(ix + 1, iy + 1, iz + 1, fx1, fy1, fz1,
periodX, periodY, periodZ);
vy1 = MathUtils.lerp(wx, vx0, vx1);
double vz1 = MathUtils.lerp(wy, vy0, vy1);
return 2.0 * MathUtils.lerp(wz, vz0, vz1);
}
public double noise1D(double x) {
int ix = MathUtils.floorInt(x);
double fx0 = x - ix;
double fx1 = fx0 - 1.0;
double wx = MathUtils.smoothStep(fx0);
double vx0 = latticeValue(ix, fx0);
double vx1 = latticeValue(ix + 1, fx1);
double vy0 = MathUtils.lerp(wx, vx0, vx1);
return 2.0 * vy0;
}
public double periodicNoise1D(double x, int periodX) {
int ix = MathUtils.floorInt(x);
double fx0 = x - ix;
double fx1 = fx0 - 1.0;
double wx = MathUtils.smoothStep(fx0);
double vx0 = periodicLatticeValue(ix, fx0, periodX);
double vx1 = periodicLatticeValue(ix + 1, fx1, periodX);
double vy0 = MathUtils.lerp(wx, vx0, vx1);
return 2.0 * vy0;
}
public double noise2D(double x, double y) {
int ix = MathUtils.floorInt(x);
double fx0 = x - ix;
double fx1 = fx0 - 1.0;
double wx = MathUtils.smoothStep(fx0);
int iy = MathUtils.floorInt(y);
double fy0 = y - iy;
double fy1 = fy0 - 1.0;
double wy = MathUtils.smoothStep(fy0);
double vx0 = latticeValue(ix, iy, fx0, fy0);
double vx1 = latticeValue(ix + 1, iy, fx1, fy0);
double vy0 = MathUtils.lerp(wx, vx0, vx1);
vx0 = latticeValue(ix, iy + 1, fx0, fy1);
vx1 = latticeValue(ix + 1, iy + 1, fx1, fy1);
double vy1 = MathUtils.lerp(wx, vx0, vx1);
double vz0 = MathUtils.lerp(wy, vy0, vy1);
return 2.0 * vz0;
}
public double periodicNoise2D(double x, double y, int periodX, int periodY) {
int ix = MathUtils.floorInt(x);
double fx0 = x - ix;
double fx1 = fx0 - 1.0;
double wx = MathUtils.smoothStep(fx0);
int iy = MathUtils.floorInt(y);
double fy0 = y - iy;
double fy1 = fy0 - 1.0;
double wy = MathUtils.smoothStep(fy0);
double vx0 = periodicLatticeValue(ix, iy, fx0, fy0, periodX, periodY);
double vx1 = periodicLatticeValue(ix + 1, iy, fx1, fy0, periodX,
periodY);
double vy0 = MathUtils.lerp(wx, vx0, vx1);
vx0 = periodicLatticeValue(ix, iy + 1, fx0, fy1, periodX, periodY);
vx1 = periodicLatticeValue(ix + 1, iy + 1, fx1, fy1, periodX, periodY);
double vy1 = MathUtils.lerp(wx, vx0, vx1);
double vz0 = MathUtils.lerp(wy, vy0, vy1);
return 2.0 * vz0;
}
public double noise(double x, double y) {
return noise(x, y, 0.0);
}
double latticeValue(int ix, int iy, int iz, double fx, double fy, double fz) {
int tabIndex = index(ix, iy, iz) * 3;
return gradientTab[tabIndex] * fx + gradientTab[tabIndex + 1] * fy
+ gradientTab[tabIndex + 2] * fz;
}
double periodicLatticeValue(int ix, int iy, int iz, double fx, double fy,
double fz, int periodX, int periodY, int periodZ) {
int tabIndex = index(MathUtils.positiveMod(ix, periodX), MathUtils
.positiveMod(iy, periodY), MathUtils.positiveMod(iz, periodZ)) * 3;
return gradientTab[tabIndex] * fx + gradientTab[tabIndex + 1] * fy
+ gradientTab[tabIndex + 2] * fz;
}
double latticeValue(int ix, int iy, double fx, double fy) {
int tabIndex = index(ix, iy) * 2;
return gradientTab[tabIndex] * fx + gradientTab[tabIndex + 1] * fy;
}
double periodicLatticeValue(int ix, int iy, double fx, double fy,
int periodX, int periodY) {
int tabIndex = index(MathUtils.positiveMod(ix, periodX), MathUtils
.positiveMod(iy, periodY)) * 2;
return gradientTab[tabIndex] * fx + gradientTab[tabIndex + 1] * fy;
}
double latticeValue(int ix, double fx) {
int tabIndex = index(ix);
return gradientTab[tabIndex] * fx;
}
double periodicLatticeValue(int ix, double fx,
int periodX) {
int tabIndex = index(MathUtils.positiveMod(ix, periodX));
return gradientTab[tabIndex] * fx;
}
int index(int ix) {
return MathUtils.hash(ix) & TAB_MASK;
}
int index(int ix, int iy) {
return MathUtils.hash(iy + MathUtils.hash(ix)) & TAB_MASK;
}
int index(int ix, int iy, int iz) {
return MathUtils.hash(iz + MathUtils.hash(iy + MathUtils.hash(ix)))
& TAB_MASK;
}
// Returns a value between -1 and 1
double getRandom() {
return 1.0 - 2.0 * rnd.nextDouble();
}
}
Here are also the functions in MathUtils that I use:
// A magic hash
public static int hash(int a) {
a = (a + 0x7ed55d16) + (a << 12);
a = (a ^ 0xc761c23c) ^ (a >> 19);
a = (a + 0x165667b1) + (a << 5);
a = (a + 0xd3a2646c) ^ (a << 9);
a = (a + 0xfd7046c5) + (a << 3);
a = (a ^ 0xb55a4f09) ^ (a >> 16);
return a;
}
// b must be positive
public static int positiveMod(int a, int b) {
int result;
if (a >= 0) {
result = a % b;
} else {
result = (b + a % b) % b;
}
return result;
}
public static double lerp(double t, double x0, double x1) {
return x0 + t * (x1 - x0);
}
public static double smoothStep(double x) {
if (x < 0.0) {
return 0.0;
}
if (x >= 1.0) {
return 1.0;
}
return (x * x * (3.0 - 2.0 * x));
}
public static int floorInt(double x) {
return (int) Math.floor(x);
}