three.js (opens new window)のうえに作られたフレームワークなので, A-Frameはthree.jsのAPIにフルアクセスできます。 ここでは基盤となるthree.jsのシーンやオブジェクト、そしてA=frameの下にあるAPIにアクセスする方法について説明します。

# A-Frameとthree.jsのシーングラフとの関係性

  • A-Frameの <a-scene> はthree.jsのシーンと一致します。
  • A-Frameの <a-entity>はthree.jsのオブジェクトと一致します。
  • three.jsのオブジェクトは.elを通じてA-Frameのエンティティを参照することができます。これはA-Frameによってセットされます。

# 親子関係

A-Frame のエンティティが親子関係でネストされている場合、three.jsのエンティティもネストされます。 例えば、このA-Frameのシーンを見てみましょう。

<a-scene>
  <a-box>
    <a-sphere></a-sphere>
    <a-light></a-light>
  </a-box>
</a-scene>

three.jsのシーングラフは応答し、次のようになります。

THREE.Scene
  THREE.Mesh
    THREE.Mesh
    THREE.Light

# three.js API にアクセスする

three.jsは ウインドウ内でグローバルオブジェクトとして利用できます。

console.log(THREE);

# three.jsのオブジェクトと一緒に動作させる

A-Frameはthree.jsの上で抽象化されていますが、three.jsの下で操作することが可能です。 A-Frameのエレメントはthree.jsのシーングラフに繋がるドアを持っています。

# three.jsのシーンにアクセスする

three.jsのシーン (opens new window).object3Dとして <a-scene> エレメントからアクセスすることが可能です。

document.querySelector('a-scene').object3D;  // THREE.Scene

A-Frameのエンティティは全て.sceneElを通じて<a-scene>を参照することができます。

document.querySelector('a-entity').sceneEl.object3D;  // THREE.Scene

コンポーネント から、そのエンティティを通じてシーンにアクセスすることができます。 (this.el):

AFRAME.registerComponent('foo', {
  init: function () {
    var scene = this.el.sceneEl.object3D;  // THREE.Scene
  }
});

# エンティティのthree.jsオブジェクトにアクセスする

A-Frameのエンティティ(例えば <a-entity>)は全てTHREE.Object3D (opens new window)を持っています。 もっと説明すると、 異なるタイプのObject3Dを内包するTHREE.Group (opens new window)です。 根底となるエンティティのTHREE.Groupには.object3Dを通じてアクセスします。

document.querySelector('a-entity').object3D;  // THREE.Group

エンティティは、複数のタイプの Object3D から構成することができます。 例えばエンティティは、THREE.MeshTHREE.Lightの両方を持つことで、ジオメトリコンポーネントとライトコンポーネント両方の性質を持つことができます。

<a-entity geometry light></a-entity>

Components add the mesh and light under the entity's root THREE.Group. References to the mesh and light are stored as different types of three.js objects in the entity's .object3DMap.

コンポーネントは、そのエンティティのルートである THREE.Group の下に、メッシュとライトを追加します。 メッシュとライトへの参照は、エンティティの .object3DMapオブジェクト内の、異なるタイプのthree.jsとして格納されます。

console.log(entityEl.object3DMap);
// {mesh: THREE.Mesh, light: THREE.Light}

But we can access them through the entity's .getObject3D(name) method: しかし、エンティティの .getObject3D(name) メソッドを使うことでアクセスすることができます。

entityEl.getObject3D('mesh');  // THREE.Mesh
entityEl.getObject3D('light');  // THREE.Light

どのようにしてthree.jsオブジェクトが最初にセットされるのかを見てみましょう。

# Object3Dをエンティティの上に配置する。

Setting an Object3D on an entity adds the Object3D to the entity's Group, which makes the newly set Object3D part of the three.js scene. We set the Object3D with the entity's .setObject3D(name) method where the name denotes the Object3Ds purpose.

For example, to set a point light from within a component:

エンティティに Object3D を設定すると、その Object3D がエンティティの Group に追加されます。 これにより、新しく設定された Object3D は three.js のシーンの一部となります。 ここではオブジェクト3Dをはエンティティの .setObject3D(name) メソッドで指定します。

例えば、コンポーネントの中から点光源を設定してみます。

AFRAME.registerComponent('pointlight', {
  init: function () {
    this.el.setObject3D('light', new THREE.PointLight());
  }
});
// <a-entity light></a-entity>

光源にlightという名前をセットしました。後でアクセスししたときには先に書いたエンティティの.getObject3D(name)メソッドを使うことができます

entityEl.getObject3D('light');

また、A-Frameのエンティティにthree.jsオブジェクトを設定すると、A-Frameは.el を介してそのエンティティに、 three.js オブジェクトから A-Frame エンティティへの参照を提供します。

entityEl.getObject3D('light').el;  // entityEl

# エンティティからObject3Dを削除する

エンティティからObject3Dを削除し、three.jsから削除するには、.removeObject3D(name)を使うことができます。 光源のサンプルに戻ると、コンポーネントが外されると光源を削除することができます。

AFRAME.registerComponent('pointlight', {
  init: function () {
    this.el.setObject3D('light', new THREE.PointLight());
  },

  remove: function () {
    // Remove Object3D.
    this.el.removeObject3D('light');
  }
});

# 作成した空間同志を変形させる

すべてのオブジェクトとシーン(世界)一般は、それぞれ独自の座標空間を持っています。 親オブジェクトの位置や回転スケールの変化は子オブジェクトの位置や回転やスケールにも適用されます。

こんなシーンを考えてみましょう。

<a-entity id="foo" position="1 2 3">
  <a-entity id="bar" position="2 3 4"></a-entity>
</a-entity>

ワールドの基準点から、fooの位置は(1,2,3)であり、fooの変換はbarの変換の上に適用されるためbarの位置は(3, 5, 7)になります。 fooの基準点からは、foo の位置は (0, 0, 0)であり、bar の位置は (2, 3, 4)となります。 これらの基準点と座標の間で変換を行いたいことがよく起こります。 上記は簡単な例ですが、次のような操作をしたい場合があります。 barの位置に対してワールドスペースでの座標を求めたり、任意のの座標をfooの座標空間へ移動させるというようなことです。 3Dプログラミングでは、これらの演算はマトリクスを使って実現しますが、three.jsはそれを簡単にするためのヘルパーを提供します。

# Local to World Transforms

Normally, we'd need to call .updateMatrixWorld () on parent Object3Ds, but three.js defaults Object3D.matrixAutoUpdate to true. We can use three.js's .getWorldPosition (vector) and .getWorldQuaternion (quaternion).

Object3Dの世界座標を入手するには

var worldPosition = new THREE.Vector3();
entityEl.object3D.getWorldPosition(worldPosition);

Object3Dの世界の回転角を入手するには

var worldQuaternion = new THREE.Quaternion();
entityEl.object3D.getWorldQuaternion(worldQuaternion);

three.jsの Object3Dローカル座標をワールド座標に変換する関数 (opens new window):を持っています。

  • .localToWorld (vector)
  • .getWorldDirection (vector)
  • .getWorldQuaternion (quaternion)
  • .getWorldScale (vector)

# 世界からローカルへの変換

世界空間からオブジェクトの局所空間への変換行列を得るには、オブジェクトのワールド行列の逆行列を行います。

var worldToLocal = new THREE.Matrix4().getInverse(object3D.matrixWorld)

これで変換したいworldToLocalの行列を適用できます。

anotherObject3D.applyMatrix(worldToLocal);