PlayCanvasのOrbit-cameraを制御して指定位置にカメラを移動させる

2023年2月27日(月) 10時7分43秒 | 179 view |

PlayCanvasのStarter Kitで用意されている「Model Viewer Starter Kit」では360°ビューワーを簡単に作る事ができますが、常にインプット情報からカメラ位置を制御しているため、スクリプトやボタンで指定位置にカメラを移動する方法にやや癖があります。
この記事ではスクリプトからorbit-camera.jsを利用した状態で指定位置にカメラを移動する方法を説明します。

これを使うとコンフィギュレーターなどを開発する際に、基本は自由移動でボタンを押したときだけ特定の部品に寄るなどの処理ができるようになります。

成果物


右下の「カメラ移動」ボタンを押すとWorld内のCone位置にカメラが移動し、向きも原点を向きます。

setPosition(), setEularAngles()は動かない

orbit-camera.jsではupdate()内で常にthis._updatePosition();を呼び出しているため、外部のスクリプトからカメラに対してsetPosition()setEularAngles()を呼び出しても次のフレームで元の位置に戻ってしまいます。
そこでorbit-camera.jsを利用している場合は、その代わりにOrbitCameraの中で定義されているresetAndLookAtPoint(resetPoint, lookAtPoint)を利用します。

resetAndLookAtPoint()

このメソッドをsetPosition()と同じように呼び出して使うことができます。
上記のサンプルではConeに以下のようにスクリプトをアタッチしています。

var Movecamera = pc.createScript('movecamera');
Movecamera.attributes.add("camera",{type:"entity"});
Movecamera.attributes.add("button",{type:"entity"});

Movecamera.prototype.initialize = function() {
    this.button.button.on('click', function(event) {
    //ボタンが押されたら、自信の位置に移動して原点を向くようにする
        this.camera.script.orbitCamera.resetAndLookAtPoint(this.entity.getLocalPosition(),new pc.Vec3(0,0,0));
    }, this);
};


setEularAngles()のような角度指定は用意されていないため、向きはlookAtされる対象座標を第2引数で入れてあげる必要があります。

resetAndLookAtEntity()

resetAndLookAtPoint()のオーバーラップ関数としてresetAndLookAtEntity()も用意されています。こちらでは第2引数でEntityを指定することで直接そちらに注視点を移すことが可能です。

Tweenを導入

デフォルトでは瞬間的に移動と視点変更が行われてしまいます。Tweenを利用することでじわりと注視点が変更するように実装することも可能です。
まずPlayCanvas公式のTweenライブラリを導入します
https://developer.playcanvas.com/ja/tutorials/tweening/

次にorbit-camera.jsresetAndLookAtPoint()を以下のように修正します。

// Set the camera position to a world position and look at a world position
// Useful if you have multiple viewing angles to swap between in a scene
OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
    // tween内でスコープが外れるため回避
    let self = this;
    // tweenの時間
    let second = 1.5;
    this.pivotPoint.copy(lookAtPoint);


    // lookAtで変化する角度を事前に取得するためのentity
    let virtualCam = new pc.Entity();
    virtualCam.setLocalPosition(resetPoint);
    virtualCam.lookAt(lookAtPoint);


    // translate tween
    let tween = this.entity.tween(this.entity.getLocalPosition()).to(resetPoint, second, pc.SineOut);
    tween.on('complete', function () {
        //実行後にpitch yawを調整する
        self.entity.lookAt(lookAtPoint);
        var distance = OrbitCamera.distanceBetween;
        distance.sub2(lookAtPoint, resetPoint);
        self.distance = distance.length();


        self.pivotPoint.copy(lookAtPoint);


        var cameraQuat = self.entity.getRotation();
        self.yaw = self._calcYaw(cameraQuat);
        self.pitch = self._calcPitch(cameraQuat, self.yaw);


        self._removeInertia();
        self._updatePosition();


        if (!self.autoRender) {
            self.app.renderNextFrame = true;
        }
    });
    tween.start();


    //rotate tween
    let tweenrotate = this.entity.tween(this.entity.getLocalEulerAngles()).rotate(virtualCam.getLocalEulerAngles(), second, pc.SineOut);
    tweenrotate.start();
};


translateとrotateのtweenを同時に走らせ、complete直後に既存の変更処理を実施するような処理としました。
rotateの情報のみlookAtで得られる最終的なEularAnglesがわからないため、事前に仮想エンティティを作成し、移動後の座標・視点をそれぞれ合わせた状態で角度を取得している点がポイントです。