Away3D TypeScriptではじめる3次元表現

第7回 回り込むカメラの真ん中にオブジェクトを捉える

この記事を読むのに必要な時間:およそ 9.5 分

原点からの円周上に複数のオブジェクトを並べる

真ん中に置いた箱の周りに,衛星のようにとりあえず5つほど箱を置きたい。これらの位置も円軌道のうえに定めるので,やはり三角関数を使う。衛星は真ん中の箱を複製して用いることにする。Mesh.clone()がそのためのメソッドだ。引数はとらない。衛星の箱をつくる関数(cloneMesh())はつぎのように定めて,複製もとのオブジェクト(箱)とつくる数を引数に渡す。

cloneMesh(複製もとオブジェクト, 数)

複製のオブジェクトをつくったら,円軌道上に置く。その位置決めは別の関数(getPolarPosition())で行う。引数には距離と角度を渡して,3次元座標をVector3Dオブジェクトの戻り値で得る。角度は度数としよう。このとき,角度の方向に気をつけたい。

getPolarPosition(距離, 角度)

オブジェクトの位置は,y軸周りの角度で決める。このとき,コンピュータグラフィックスではy軸は下が正の向きだ。そして,その向きに時計回りが角度の正方向とされる。つまり,座標空間を上から見下ろしたxz平面で,時計回りが正となる図6)⁠これは三角関数で扱う角度と向きが逆だ※3)⁠したがって,三角関数には角度の値を,正負逆にして与えなければならない。

図6 xz平面でy軸周りの角度は時計回りが正

図6 xz平面でy軸周りの角度は時計回りが正

オブジェクトを複製する関数(cloneMesh())は,つぎのように初期設定の関数(initialize())から呼び出す。複製するオブジェクトは,引数に受け取った個数(count)にもとづいて均等の角度(degrees)で置くことにした。原点からの距離(distance)も等しくし,位置決めの関数(getPolarPosition())にその値と角度を渡して得た戻り値(position)から,オブジェクトの3次元座標が定められている。なお前述のとおり,位置決めの関数の中で,y軸周りの角度(rotationY)は正負逆にしてある。

function initialize() {

  cloneMesh(cube, 5);

}

function cloneMesh(mesh, count) {
  var scene = view.scene;
  var distance = 1000;
  var degrees = 360 / count;
  for (var i = 0; i < count; i++) {
    var clone = mesh.clone();
    var rotationY = degrees * i;
    var position = getPolarPosition(distance, rotationY);
    clone.x = position.x;
    clone.y = position.y;
    clone.z = position.z;
    clone.rotationY = rotationY;
    scene.addChild(clone);
  }
}

function getPolarPosition(distance, rotationY) {
  var vector = new Vector3D();
  vector.x = distance * Math.cos(-rotationY * Math.PI / 180);
  vector.z = distance * Math.sin(-rotationY * Math.PI / 180);
  return vector;
}

ひとつ補っておきたいのは,オブジェクトを複製する関数(cloneMesh())の中でオブジェクト(clone)そのものを配置したのと同じ角度(rotationY)回したことだ。こうしないと,オブジェクトはすべて真ん中の箱と同じ向きになる。観覧車のゴンドラと同じ状態だ。そうではなく,原点と自分を結んだ直線の向きになるようにした。つまり,カメラがオブジェクトを画面の真ん中に捉えたとき,つねに正面を向くことになる。

これで,真ん中のオブジェクトを中心とした円周上に,等しい間隔で複数のオブジェクトが並べられた図7)⁠カメラはその外の円軌道を回り込んで,真ん中の箱を画面の中心に捉える。以下のコード3に,ここまででき上がったスクリプトをまとめた。同じコードは,jsdo.itにサンプル2として掲げてある。次回は,周りに配置するオブジェクトの位置や大きさをランダムにしてみよう。

図7 真ん中の箱を中心とした円周上に複数の箱が置かれた

図7 真ん中の箱を中心とした円周上に複数の箱が置かれた

サンプル2 Away3D 14/11/05: Cubes on abcircle with the camera rotated around them

コード3 円軌道上に複数のオブジェクトを配置してカメラで回り込む

var LoaderEvent = require("awayjs-core/lib/events/LoaderEvent");
var Vector3D = require("awayjs-core/lib/geom/Vector3D");
var AssetLibrary = require("awayjs-core/lib/library/AssetLibrary");
var URLRequest = require("awayjs-core/lib/net/URLRequest");
var RequestAnimationFrame = require("awayjs-core/lib/utils/RequestAnimationFrame");
var View = require("awayjs-display/lib/containers/View");
var DirectionalLight = require("awayjs-display/lib/entities/DirectionalLight");
var StaticLightPicker = require("awayjs-display/lib/materials/lightpickers/StaticLightPicker");
var PrimitiveCubePrefab = require("awayjs-display/lib/prefabs/PrimitiveCubePrefab");
var DefaultRenderer = require("awayjs-renderergl/lib/DefaultRenderer");
var TriangleMethodMaterial = require("awayjs-methodmaterials/lib/TriangleMethodMaterial");
var view;
var cube;
var imageDiffuse = "assets/trinket_diffuse.jpg";
var timer;
var ORIGIN = new Vector3D();
var angle = -Math.PI / 2;
var distance = 1500;
var stageWidth = 240;
var stageHeight = 180;
function initialize() {
  var directionalLight = createDirectionalLight(0.5, 0xFFFFFF);
  view = createView(stageWidth, stageHeight, 0x0);
  cube = createCube(400, 400, 400, directionalLight);
  setCamera(view.camera, distance, angle);
  view.scene.addChild(cube);
  cloneMesh(cube, 5);
  AssetLibrary.addEventListener(LoaderEvent.RESOURCE_COMPLETE, onResourceComplete);
  AssetLibrary.load(new URLRequest(imageDiffuse));
  timer = new RequestAnimationFrame(rotate);
  timer.start();
  view.render();
}
function createView(width, height, backgroundColor) {
  var defaultRenderer = new DefaultRenderer();
  var view = new View(defaultRenderer);
  view.width = width;
  view.height = height;
  view.backgroundColor = backgroundColor;
  return view;
}
function createCube(width, height, depth, light) {
  var material = new TriangleMethodMaterial();
  var cube = new PrimitiveCubePrefab(width, height, depth, 1, 1, 1, false)
  .getNewObject();
  cube.material = material;
  material.lightPicker = new StaticLightPicker([light]);
  return cube;
}
function cloneMesh(mesh, count) {
  var scene = view.scene;
  var distance = 1000;
  var degrees = 360 / count;
  for (var i = 0; i < count; i++) {
    var clone = mesh.clone();
    var rotationY = degrees * i;
    var position = getPolarPosition(distance, rotationY);
    clone.x = position.x;
    clone.y = position.y;
    clone.z = position.z;
    clone.rotationY = rotationY;
    scene.addChild(clone);
  }
}
function createDirectionalLight(ambient, color) {
  var light = new DirectionalLight();
  light.ambient = ambient;
  light.color = color;
  return light;
}
function onResourceComplete(eventObject) {
  var assets = eventObject.assets;
  var material = cube.material;
  material.texture = assets[0];
  view.render();
}
function rotate(timeStamp) {
  var camera = view.camera;
  angle += timeStamp / 2000;
  setCamera(camera, distance, angle);
  view.render();
}
function setCamera(camera, distance, angle) {
  camera.x = Math.cos(angle) * distance;
  camera.z = Math.sin(angle) * distance;
  camera.lookAt(ORIGIN);
}
function getPolarPosition(distance, rotationY) {
  var vector = new Vector3D();
  vector.x = distance * Math.cos(-rotationY * Math.PI / 180);
  vector.z = distance * Math.sin(-rotationY * Math.PI / 180);
  return vector;
}
※3
今回の作例ではどちらを正方向にしても,とくに問題は起こらない。しかし,後にAway3Dのメソッドで回転を扱う。そのとき,向きが違っていると混乱する。そのため,今のうちに揃えておく。

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書