The future starts today

Webとか英語とか育児とかに関する雑記

【Three.js】perlin-noiseを用いてなだらかな地形を生成する

最近スマホ版のマインクラフトにどハマりしていて、ある時「これthree.jsで作れるのでは?」と思い立ち、作り始めた。

three.jsの記事を色々と探していると、だいたいHTMLにscriptタグでthree.jsを読み込んで、立方体や球を表示させて、回転させてみて、終わり〜という記事ばかりであまり参考にならなかった。

とりあえずNode環境で実装したかったので、npmパッケージを探すところからスタート。

three.jsを扱えるnpmパッケージを使う

www.npmjs.com

以下のような形で使うことができる。

import THREE from 'three.js';
import keyEvents from './keyEvents';
import { createMap } from './map';

const main = function() {
  const scene  = new THREE.Scene(),
    width = window.innerWidth,
    height = window.innerHeight,
    fov = 60,
    aspect = width / height,
    near = 1,
    far = 1000,
    camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.set(0, 20, 0);

  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(width, height);
  document.body.appendChild(renderer.domElement);

  const directionalLight = new THREE.DirectionalLight(0xffffff);
  directionalLight.position.set(0, 0.7, 0.7);
  scene.add(directionalLight);

  // set map
  createMap(scene);

  renderer.render(scene, camera);

  // set key events
  document.addEventListener('keydown', (e) => keyEvents(e, renderer, scene, camera));
}

window.addEventListener('DOMContentLoaded', main, false);

なだらかな地形を生成する

最初は立方体をひたすら敷き詰めて平面を作った。
しかし、実際マインクラフトはいい感じに地形が形成されている。
それっぽくなだらかな凹凸を出すためにはどうすれば良いのか悩んだ結果、perlin-noiseというパッケージを見つけた。

www.npmjs.com

中身を見るととても短い実装だが、とりあえず今は動けば良い。

perlin-noiseの使い方

generatePerlinNoise(width, height)を実行するとwidth * height分のlengthを持った配列が返ってくる。 各要素には0~1の値が格納されており、縦横にループで回すと良い感じのy座標が得られるという仕組みだ。

以下のように実装できる。

import THREE from 'three.js';
import perlin from 'perlin-noise';
import config from './config/blocks';

const X_LENGTH = 100;
const Y_LENGTH = 100;

class Block {
  constructor(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
  setMesh() {
    const material = new THREE.MeshPhongMaterial({ color: config.color, side: THREE.DoubleSide });
    this.mesh = new THREE.Mesh(geometry, material);
    this.mesh.position.set(this.x, this.y, this.z);
  }
  getMesh() {
    return this.mesh;
  }
}

export const createMap = (scene) => {
  const yArray = perlin.generatePerlinNoise(X_LENGTH, Y_LENGTH);
  for (let i = 0; i < X_LENGTH; i++) {
    for (let j = 0; j < Y_LENGTH; j++) {
        const y = Math.round(yArray[i * Y_LENGTH + j] * 3) * 10 - 25;
        const block = new Block(-X_LENGTH*10/2 + i * 10, y, -Y_LENGTH*10/2 + j * 10);
        block.setMesh();
        scene.add(block.getMesh());
    }
  }
};

すると、こんな感じの地形が作れる。

f:id:shibe97:20170328231322p:plain

まとめ

スター数を見た感じ、もっとメジャーなライブラリがありそう。
一旦やりたいことは実現できたので良しとする。