2

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.

alon
  • 50
  • 7
  • You could close holes, average, and relax tiles... but the continuity of the Perlin noise will be lost. For example, mountains will end square into rivers (Half Dome). You should do it in a single pass or specify a seed constant. Also, if you're world-building and expect undo states you must save the initial tile data. Maybe, best of both worlds, there is a major/minor elevation with a large static tile height and smaller detail heights. Many ocean/wave examples. – PartialFlavor_55KP Nov 29 '21 at 21:31

1 Answers1

2

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>
prisoner849
  • 16,894
  • 4
  • 34
  • 68
  • Very clever solution, I was using this example terrain generation method: [geometry_terrain_raycast](https://threejs.org/examples/?q=terra#webgl_geometry_terrain_raycast), with this method generating terrain is so much easier – alon Nov 30 '21 at 18:35
  • Sorry for the thread revival, but do you know a way to convert the planes to CANNON.js Heightfields or Trimesh? I've seen _[link](https://github.com/donmccurdy/three-to-cannon)_ but I cannot find any way to import it. I am looking for a way to store the height values in a matrix and use that matrix to create a Heightfield like this: _[link](https://schteppe.github.io/cannon.js/demos/heightfield.html)_ – alon Dec 23 '21 at 22:58