[別の記事](threejs-custom-buffergeometry.html)で、カスタムジオメトリの作成について説明します。
今はそれぞれの種類のプリミティブを作成する例を作ってみます。
[以前の記事](threejs-responsive.html)を例に始めましょう。
最初の方で、背景色を指定します。
```js
const scene = new THREE.Scene();
+scene.background = new THREE.Color(0xAAAAAA);
```
これでthree.jsに、透明からライトグレーに変えるように伝えます。
全てのオブジェクトを見られるよう、カメラの位置も変える必要があります。
```js
-const fov = 75;
+const fov = 40;
const aspect = 2; // the canvas default
const near = 0.1;
-const far = 5;
+const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
-camera.position.z = 2;
+camera.position.z = 120;
```
`addObject`関数を加えましょう。これはx座標とy座標と`Object3D`を取り、シーンにオブジェクトを追加します。
```js
const objects = [];
const spread = 15;
function addObject(x, y, obj) {
obj.position.x = x * spread;
obj.position.y = y * spread;
scene.add(obj);
objects.push(obj);
}
```
ランダムに色付けされたマテリアルを作る関数も作成してみましょう。
色相、彩度、輝度に基づいて色を設定できる、`Color`の機能を使ってみます。
`hue`は色相環を0から1まで変化します。赤は0、緑は.33、青は.66です。
`saturation`は0から1まで変化します。0 は無色で、1は最も彩度の高いです。
`luminance`は0から1まで変化します。0は黒、1は白、0.5が色の最大量になります。
言い換えると、`luminance`が0.0から0.5に変化するにつれて、色は黒から`hue`に
変わります。0.5から1.0に変化するにつれて、`hue`から白に変化します。
```js
function createMaterial() {
const material = new THREE.MeshPhongMaterial({
side: THREE.DoubleSide,
});
const hue = Math.random();
const saturation = 1;
const luminance = .5;
material.color.setHSL(hue, saturation, luminance);
return material;
}
```
私たちは`side: THREE.DoubleSide`もマテリアルに渡しました。
これはthreeに形状を作るときに三角形の両面を描くように指示します。
球体や立方体のような立体形状には、形状の内側を向いている裏側を描く
理由はありません。
しかしこの例だと、2次元で裏側が存在しない`PlaneGeometry`や`ShapeGeometry`のようなものも描こうとしています。
`side: THREE.DoubleSide`を設定しないと、裏側を見たときに消えてしまうことでしょう。
`side: THREE.DoubleSide`に**not**が設定された方が、描画が速くなります。
そのため、理想的には本当に必要なときだけ設定するのが良いことを注記しておきます。
しかしこの例だと、そんなにたくさん描画しないので心配ありません。
`addSolidGeometry`関数を作りましょう。ジオメトリを渡すと`createMaterial`によってランダムに色が付いたマテリアルを作り、`addObject`によってシーンに追加してくれます。
```js
function addSolidGeometry(x, y, geometry) {
const mesh = new THREE.Mesh(geometry, createMaterial());
addObject(x, y, mesh);
}
```
これで私たちの作るプリミティブの大多数に、この関数が使用できます。
例えば、立方体を作ってみます。
```js
{
const width = 8;
const height = 8;
const depth = 8;
addSolidGeometry(-2, -2, new THREE.BoxGeometry(width, height, depth));
}
```
下記のコードを覗いてみると、それぞれの種類のジオメトリに対して、同じような箇所があります。
結果はこのようになります:
{{{example url="../threejs-primitives.html" }}}
上記のパターンには、2つの特筆すべき例外があります。
一番大きなものは、たぶん`TextGeometry`です。テキストのメッシュを作るときは、事前に3Dフォントデータを読み込む必要があります。このデータの読み込みは非同期的に行われるので、ジオメトリを作ろうとする前に、読み込みを待つ必要があります。フォントの読み込みにpromiseを使うと、もっと速く読み込むことができます。
`FontLoader`を作成し、読み込みが完了するとフォントを提供してくれるpromiseを返す`loadFont`関数を作ります。
次に、`doit` と呼ばれる`async`関数を作り、`await`を使ってフォントを読み込みます。
最後に、ジオメトリを作り、`addObject`を呼んでシーンに追加します。
```js
{
const loader = new THREE.FontLoader();
// promisify font loading
function loadFont(url) {
return new Promise((resolve, reject) => {
loader.load(url, resolve, undefined, reject);
});
}
async function doit() {
const font = await loadFont('resources/threejs/fonts/helvetiker_regular.typeface.json'); /* threejsfundamentals: url */
const geometry = new THREE.TextGeometry('three.js', {
font: font,
size: 3.0,
height: .2,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.15,
bevelSize: .3,
bevelSegments: 5,
});
const mesh = new THREE.Mesh(geometry, createMaterial());
geometry.computeBoundingBox();
geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1);
const parent = new THREE.Object3D();
parent.add(mesh);
addObject(-1, -1, parent);
}
doit();
}
```
また、もう一つ違いがあります。私たちはテキストを、自身の中心の周りで回転させたかったのですが、
three.jsはデフォルトで、テキストを左端中心に回転するよう作成します。
これを回避するため、three.jsにジオメトリのバウンディングボックスの計算をさせることができます。
バウンディングボックスの`getCenter`メソッドを呼ぶことができるので、それにメッシュの位置オブジェクトに渡します。
すると、`getCenter`が箱の中心をその位置にコピーします。このとき、位置オブジェクトも返すので、回転の中心が物体の中心になるように、オブジェクト全体の位置に対して`multiplyScalar(-1)`を呼ぶことができます。
これだと、もし先の例のように`addSolidGeometry`を呼ぶと、
再び位置が設定されてしまいますが、それはよくありませんよね。
そのためこの例では、three.jsのシーングラフの標準的なノードである`Object3D`を作ります。
`Mesh`は同様に`Object3D`を継承しています。
[別の記事](threejs-scenegraph.html)でどのようにシーングラフが働くかカバーします。
今はとりあえず、DOMノードのように、子ノードは親ノードと関連して描画されると知っていれば十分です。
`Object3D`を作成し、メッシュをその子にすることで、どこにでも`Object3D`に配置し、
先ほど設定した中心のオフセットを維持したままにできます。
こうしないと、テキストが中央からずれて回ってしまうことになります。
{{{example url="../threejs-primitives-text.html" }}}
左側のものは自身の中心の周りを回転していませんが、右側のものはそうなっていることに
注意してください。
もう一つの例外は、`EdgesGeometry`と`WireframeGeometry`の、2つの直線に基づいた例です。
`addSolidGeometry`を呼ぶ代わりに、このように`addLineGeometry`を呼んでいます。
```js
function addLineGeometry(x, y, geometry) {
const material = new THREE.LineBasicMaterial({color: 0x000000});
const mesh = new THREE.LineSegments(geometry, material);
addObject(x, y, mesh);
}
```
黒色の`LineBasicMaterial`を作り、次に`LineSegments`オブジェクトを作成しています。
これは`Mesh`のラッパーで、あなたが線分(線分あたり2点)を描画しようとしていることを
threeが知る手助けをします。
プリミティブのそれぞれは、作成時に渡すことができる複数のパラメーターを持っていて、
ここで繰り返し説明するよりも[このドキュメント](https://threejs.org/docs/)を覗いてもらうのが最善です。
また、各形状の横にある上記のリンクをクリックすると、その形状のドキュメントに直接案内されます。
上記パターンに全然当てはまらないクラスの組があります。
それは`PointsMaterial`と`Points`クラスです。`Points`は`LineSegments`に似ていて、
`Geometry`か`BufferGeometry`を引数に取ります。しかし、線の代わりに各頂点の点を描画します。
使うためには、`PointsMaterial`も渡す必要があります。
これは、点をどれくらい大きくするか決めるため[`size`](PointsMaterial.size) を引数に取ります。
```js
const radius = 7;
const widthSegments = 12;
const heightSegments = 8;
const geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);
const material = new THREE.PointsMaterial({
color: 'red',
size: 0.2, // in world units
});
const points = new THREE.Points(geometry, material);
scene.add(points);
```
カメラからの距離に関わらず点の大きさを同じにしたいなら、[`sizeAttenuation`](PointsMaterial.sizeAttenuation) をfalseにすることで、サイズ変更を止めることができます。
```js
const material = new THREE.PointsMaterial({
color: 'red',
+ sizeAttenuation: false,
+ size: 3, // in pixels
- size: 0.2, // in world units
});
...
```