What is the best approach to make procedural world generation in three.js?

Viewed 695

I have the function CreateChunk(x,z) that creates a "chunk" of terrain in the specified coordinates x and z that is a plane, whose vertex heights are modified with Perlin noise and then painted based on their height (a layer of water is added too) as you see below :

A single chunk

Everything works fine until I try to make more chunks:

Many chunks

I know this is how it should work and there is nothing wrong, but, what can I do to "synchronize" them so where one ends, the other starts? While keeping a procedural generation.

If you need the code tell me, but I was just asking for an idea to follow.

1 Answers

You need to know what tile you want to build and what density of noise you want to have on the tiles.

For some ideas, have a look at this forum post: https://discourse.threejs.org/t/help-getting-the-actual-position-of-a-vertices-in-a-buffer-geometry/29649/4

And I'll leave the snippet here. Maybe it will be helpful for other seekers :)

body{
  overflow: hidden;
  margin: 0;
}
<script type="module">
  import * as THREE from "https://cdn.skypack.dev/three@0.135.0";
import {OrbitControls} from "https://cdn.skypack.dev/three@0.135.0/examples/jsm/controls/OrbitControls";
import {ImprovedNoise} from "https://cdn.skypack.dev/three@0.135.0/examples/jsm/math/ImprovedNoise";

THREE.BufferGeometry.prototype.toQuads = ToQuads;

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 8, 13);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
})

const perlin = new ImprovedNoise();

let controls = new OrbitControls(camera, renderer.domElement);

let step = 20;
for(let z = -4; z <= 4; z ++){
  for(let x = -4; x <= 4; x++){
    let p = createPlane(step, Math.random() * 0x7f7f7f + 0x7f7f7f);
    setNoise(p.geometry, new THREE.Vector2(x, z), 2, 5);
    p.geometry.rotateX(Math.PI * 0.5);
    p.position.set(x, 0, z).multiplyScalar(step);
    scene.add(p);
  }
}

renderer.setAnimationLoop( _ => {
    renderer.render(scene, camera);
})

function createPlane( step, color){
  let g = new THREE.PlaneGeometry(step, step, 25, 25).toQuads();
  let m = new THREE.LineBasicMaterial({color: color});
  let l = new THREE.LineSegments(g, m);
  return l;
}

function setNoise(g, uvShift, multiplier, amplitude){
  let pos = g.attributes.position;
  let uv = g.attributes.uv;
  let vec2 = new THREE.Vector2();
  for(let i = 0; i < pos.count; i++){
    vec2.fromBufferAttribute(uv, i).add(uvShift).multiplyScalar(multiplier);
    pos.setZ(i, perlin.noise(vec2.x, vec2.y, 0) * amplitude );
  }
}


function ToQuads() {
  let g = this;
  let p = g.parameters;
  let segmentsX = (g.type == "TorusBufferGeometry" ? p.tubularSegments : p.radialSegments) || p.widthSegments || p.thetaSegments || (p.points.length - 1) || 1;
  let segmentsY = (g.type == "TorusBufferGeometry" ? p.radialSegments : p.tubularSegments) || p.heightSegments || p.phiSegments || p.segments || 1;
  let indices = [];
  for (let i = 0; i < segmentsY + 1; i++) {
    let index11 = 0;
    let index12 = 0;
    for (let j = 0; j < segmentsX; j++) {
      index11 = (segmentsX + 1) * i + j;
      index12 = index11 + 1;
      let index21 = index11;
      let index22 = index11 + (segmentsX + 1);
      indices.push(index11, index12);
      if (index22 < ((segmentsX + 1) * (segmentsY + 1) - 1)) {
        indices.push(index21, index22);
      }
    }
    if ((index12 + segmentsX + 1) <= ((segmentsX + 1) * (segmentsY + 1) - 1)) {
      indices.push(index12, index12 + segmentsX + 1);
    }
  }
  g.setIndex(indices);
  return g;
}
</script>

Related