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

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

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

立方体を中心に三角関数でカメラを回す

つぎは,カメラを動かそう。立方体を真ん中にして回るようにする。記憶のよい読者は,前回第6回のスカイボックスの中にドーナッツ型を置くの項で行ったと思い出したかもしれない。しかし,おさらいばかりではつまらない。引出しを増やす意味でも別のやり方を使ってみよう。

カメラの向きは後に回し,立方体を中心として水平に円を描くように動かす。円軌道の座標は,三角関数のsinとcosで求める。原点O(0, 0)を中心に描いた半径1の円(⁠単位円」と呼ぶ)について,原点Oからx軸正方向に対して角度θの直線と交わる点Pのxy座標は三角関数により(cosθ, sinθ)と定められている図3)⁠つまり,原点からの距離は1で角度がθの点のxy座標は(cosθ, sinθ)ということだ※2)⁠すると,原点からの距離と角度をもとに,円周上のxy座標はつぎの式で導かれる。

x = 距離×cos角度
y = 距離×sin角度

図3 原点から距離が1で角度θのxy座標は(cosθ, sinθ)

図3 原点から距離が1で角度θのxy座標は(cosθ, sinθ)

では,カメラはどうやって立方体に向ければよいか。動かすたびに,カメラに対してDisplayObject.lookAt()メソッドを呼び出せばよい(第6回のスカイボックスの中にドーナッツ型を置くの項を参照)⁠立方体は3次元空間の真ん中に置いたので,向ける座標は原点(0, 0, 0)だ。

カメラを動かす関数(setCamera())は,つぎのように定めよう。引数にはCameraオブジェクトと距離および角度を渡す。Mathクラスの三角関数を使うので,角度の単位はラジアンとする。

setCamera(カメラ, 距離, 角度)

前掲コード1は,つぎのように手直しする。カメラを動かす関数(setCamera())は,まず初期設定の関数(initialize())から呼び出す。距離と角度は変数(distanceとangle)に定めた。なお,角度の初期値(-π/2 = -90度)は立方体の手前正面になる。そして,アニメーションの関数(rotate())が,経過時間(timeStamp)に応じて角度を増し,そのたびにカメラを動かす関数が呼び出される。

var Vector3D = require("awayjs-core/lib/geom/Vector3D");

var ORIGIN = new Vector3D();
var angle = -Math.PI / 2;
var distance = 1500;

function initialize() {

  setCamera(view.camera, distance, angle);

}

function rotate(timeStamp) {
  // cube.rotationX = (cube.rotationX + 1) % 360;
  // cube.rotationY = (cube.rotationY + 1) % 360;
  var camera = view.camera;
  angle += timeStamp / 2000;
  setCamera(camera, distance, angle);

}
function setCamera(camera, distance, angle) {
  camera.x = Math.cos(angle) * distance;
  camera.z = Math.sin(angle) * distance;
  camera.lookAt(ORIGIN);
}

この機会に,描画領域(view)の幅と高さは変数(stageWidthとstageHeight)に定めた。

var stageWidth = 240;
var stageHeight = 180;
function initialize() {

  // view = createView(240, 180, 0x0);
  view = createView(stageWidth, stageHeight, 0x0);

}

これで,立方体を3次元空間の真ん中に捉えつつ,カメラがその周りを回る図4)⁠手を加えた後のスクリプト全体は以下のコード2のとおりだ。お題の土台ができたといえる。

図4 真ん中に置いた立方体の周りをカメラが回る

図4 真ん中に置いた立方体の周りをカメラが回る

コード2 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);
  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 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);
}

すでに述べたとおり,このようなカメラの動きは前回と同じようにTransform.moveBackward()メソッドを用いてもできる。三角関数を使うことで,何か得はあるのだろうか。たとえば,sinとcosを使って楕円軌道を描くこともできる。その場合には,x座標とy座標それぞれを求める距離の大きさを変えればよい。

x = 距離×cos角度×比率
y = 距離×sin角度

試しに,前掲コード2のカメラを動かす関数(setCamera())で,座標を求める式の一方につぎのように比率(0.5)を乗じてみる。すると,カメラは楕円軌道を動くので,立方体に近づいたり離れたりすることになる図5)⁠もし,Transform.moveBackward()メソッドで後ろに下げようとすれば,その距離を求めなければならず,三角関数を使わざるを得ない。それなら,初めから三角関数でカメラそのものを動かすほうが早い。

function setCamera(camera, distance, angle) {
  camera.x = Math.cos(angle) * distance * 0.5;
  camera.z = Math.sin(angle) * distance;
  camera.lookAt(ORIGIN);
}

図5 楕円軌道を回るとカメラは箱に近づいたり離れたりする

図5 楕円軌道を回るとカメラは箱に近づいたり離れたりする 図5 楕円軌道を回るとカメラは箱に近づいたり離れたりする

※2
これは三角関数のsinとcosの定義だ。直角三角形の辺の比で覚えた人も多いかもしれない。しかし,この図で理解した方が応用の幅は広がる。興味のある読者は,sinとcosは何する関数?を読んでほしい(15分間のビデオでも解説している)⁠

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入