【Three.js】perlin-noiseを用いてなだらかな地形を生成する
最近スマホ版のマインクラフトにどハマりしていて、ある時「これthree.jsで作れるのでは?」と思い立ち、作り始めた。
three.jsの記事を色々と探していると、だいたいHTMLにscriptタグでthree.jsを読み込んで、立方体や球を表示させて、回転させてみて、終わり〜という記事ばかりであまり参考にならなかった。
とりあえずNode環境で実装したかったので、npmパッケージを探すところからスタート。
three.jsを扱えるnpmパッケージを使う
以下のような形で使うことができる。
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
というパッケージを見つけた。
中身を見るととても短い実装だが、とりあえず今は動けば良い。
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()); } } };
すると、こんな感じの地形が作れる。
まとめ
スター数を見た感じ、もっとメジャーなライブラリがありそう。
一旦やりたいことは実現できたので良しとする。