Home Manual Reference Source

src/basic/S3Camera.js

import S3Math from "../math/S3Math.js";
import S3Vector from "../math/S3Vector.js";
import S3Matrix from "../math/S3Matrix.js";
import S3System from "./S3System.js";

/**
 * 3DCGシーンのカメラ(視点)情報を管理するクラス
 * 視点座標、注視点、視野角、描画範囲、各種行列演算などを保持・操作します。
 *
 * @class
 * @module S3
 */
export default class S3Camera {
	/**
	 * カメラを作成します。
	 * @param {S3System} s3system S3Systemインスタンス
	 */
	constructor(s3system) {
		/**
		 * システムインスタンス
		 * @type {S3System}
		 */
		this.sys = s3system;
		this.init();
	}

	/**
	 * カメラの状態を初期化します(初期パラメータにリセット)。
	 */
	init() {
		/**
		 * 上下方向の視野角(度単位)
		 * @type {number}
		 */
		this.fovY = 45;

		/**
		 * 視点(カメラの位置ベクトル)
		 * @type {S3Vector}
		 */
		this.eye = new S3Vector(0, 0, 0);

		/**
		 * 注視点(カメラが見ている位置ベクトル)
		 * @type {S3Vector}
		 */
		this.at = new S3Vector(0, 0, 1);

		/**
		 * 描画範囲の最近接面(ニアクリップ)
		 * @type {number}
		 */
		this.near = 1;

		/**
		 * 描画範囲の最遠面(ファークリップ)
		 * @type {number}
		 */
		this.far = 1000;
	}

	/**
	 * カメラを破棄します(プロパティを初期化)。
	 */
	dispose() {
		this.sys = null;
		this.fovY = 0;
		this.eye = null;
		this.at = null;
		this.near = 0;
		this.far = 0;
	}

	/**
	 * このカメラのクローン(複製)を作成します。
	 * @returns {S3Camera} 複製されたS3Cameraインスタンス
	 */
	clone() {
		const camera = new S3Camera(this.sys);
		camera.fovY = this.fovY;
		camera.eye = this.eye;
		camera.at = this.at;
		camera.near = this.near;
		camera.far = this.far;
		return camera;
	}

	/**
	 * カメラのビュー・プロジェクション・ビューポート行列情報をまとめた型
	 *
	 * - LookAt: ビュー変換行列
	 * - aspect: アスペクト比(canvas幅 / 高さ)
	 * - PerspectiveFov: パースペクティブ射影行列
	 * - Viewport: ビューポート変換行列
	 *
	 * @typedef {Object} S3VPSMatrix
	 * @property {S3Matrix} LookAt         ビュー(LookAt)変換行列
	 * @property {number} aspect           アスペクト比
	 * @property {S3Matrix} PerspectiveFov パースペクティブ射影行列
	 * @property {S3Matrix} Viewport       ビューポート変換行列
	 */

	/**
	 * カメラのビュー・プロジェクション・ビューポート行列(VPS)をまとめて取得します。
	 * 通常は描画や座標変換時の各種行列一式の取得に使います。
	 *
	 * @param {HTMLCanvasElement} canvas 描画先となるcanvas要素
	 * @returns {S3VPSMatrix}
	 */
	getVPSMatrix(canvas) {
		const x = S3System.calcAspect(canvas.width, canvas.height);
		// ビューイング変換行列を作成する
		const V = this.sys.getMatrixLookAt(this.eye, this.at);
		// 射影トランスフォーム行列
		const P = this.sys.getMatrixPerspectiveFov(this.fovY, x, this.near, this.far);
		// ビューポート行列
		const S = this.sys.getMatrixViewport(0, 0, canvas.width, canvas.height);
		return { LookAt: V, aspect: x, PerspectiveFov: P, Viewport: S };
	}

	/**
	 * 描画範囲(ニア・ファー)を設定します。
	 * @param {number} near 最近接面
	 * @param {number} far 最遠面
	 */
	setDrawRange(near, far) {
		this.near = near;
		this.far = far;
	}

	/**
	 * 上下方向の視野角を設定します(度単位)。
	 * @param {number} fovY 視野角
	 */
	setFovY(fovY) {
		this.fovY = fovY;
	}

	/**
	 * 視点(eye)を設定します。
	 * @param {S3Vector} eye 新しい視点ベクトル
	 */
	setEye(eye) {
		this.eye = eye.clone();
	}

	/**
	 * 注視点(at)を設定します。
	 * @param {S3Vector} at 新しい注視点ベクトル
	 */
	setCenter(at) {
		this.at = at.clone();
	}

	/**
	 * 現在の視線ベクトル(at→eye方向の単位ベクトル)を取得します。
	 * @returns {S3Vector} 正規化済みの視線方向
	 */
	getDirection() {
		return this.eye.getDirectionNormalized(this.at);
	}

	/**
	 * カメラと注視点の距離を取得します。
	 * @returns {number} 距離
	 */
	getDistance() {
		return this.at.getDistance(this.eye);
	}

	/**
	 * 注視点から一定距離の位置に視点を設定します。
	 * @param {number} distance 距離
	 */
	setDistance(distance) {
		const direction = this.at.getDirectionNormalized(this.eye);
		this.eye = this.at.add(direction.mul(distance));
	}

	/**
	 * カメラの水平方向(Y軸回転)の角度を取得します(度単位)。
	 * @returns {number} Y軸回転角(度)
	 */
	getRotateY() {
		const ray = this.at.getDirection(this.eye);
		return S3Math.degrees(Math.atan2(ray.x, ray.z));
	}

	/**
	 * 水平方向(Y軸回転)の角度を設定します(度単位)。
	 * @param {number} deg Y軸回転角(度)
	 */
	setRotateY(deg) {
		const rad = S3Math.radius(deg);
		const ray = this.at.getDirection(this.eye);
		const length = ray.setY(0).norm();
		const cos = Math.cos(rad);
		const sin = Math.sin(rad);
		this.eye = new S3Vector(this.at.x + length * sin, this.eye.y, this.at.z + length * cos);
	}

	/**
	 * Y軸回転角を相対的に加算します(度単位)。
	 * @param {number} deg 加算する角度(度)
	 */
	addRotateY(deg) {
		this.setRotateY(this.getRotateY() + deg);
	}

	/**
	 * カメラの垂直方向(X軸回転)の角度を取得します(度単位)。
	 * @returns {number} X軸回転角(度)
	 */
	getRotateX() {
		const ray = this.at.getDirection(this.eye);
		return S3Math.degrees(Math.atan2(ray.z, ray.y));
	}

	/**
	 * 垂直方向(X軸回転)の角度を設定します(度単位)。
	 * @param {number} deg X軸回転角(度)
	 */
	setRotateX(deg) {
		const rad = S3Math.radius(deg);
		const ray = this.at.getDirection(this.eye);
		const length = ray.setX(0).norm();
		const cos = Math.cos(rad);
		const sin = Math.sin(rad);
		this.eye = new S3Vector(this.eye.x, this.at.y + length * cos, this.at.z + length * sin);
	}

	/**
	 * X軸回転角を相対的に加算します(度単位)。
	 * @param {number} deg 加算する角度(度)
	 */
	addRotateX(deg) {
		this.setRotateX(this.getRotateX() + deg);
	}

	/**
	 * ワールド座標系で絶対移動します。
	 * @param {S3Vector} v 移動ベクトル
	 */
	translateAbsolute(v) {
		this.eye = this.eye.add(v);
		this.at = this.at.add(v);
	}

	/**
	 * カメラのローカル座標系で相対移動します。
	 * @param {S3Vector} v 移動ベクトル
	 */
	translateRelative(v) {
		let X, Y, Z;
		const up = new S3Vector(0.0, 1.0, 0.0);
		// Z ベクトルの作成
		Z = this.eye.getDirectionNormalized(this.at);

		// 座標系に合わせて計算
		if (this.sys.dimensionmode === S3System.DIMENSION_MODE.RIGHT_HAND) {
			// 右手系なら反転
			Z = Z.negate();
		}
		// X, Y ベクトルの作成
		X = up.cross(Z).normalize();
		Y = Z.cross(X);
		// 移動
		X = X.mul(v.x);
		Y = Y.mul(v.y);
		Z = Z.mul(v.z);
		this.translateAbsolute(X.add(Y).add(Z));
	}

	/**
	 * カメラのパラメータを文字列で出力します。
	 * @returns {string} 視点・注視点・視野角の情報を含む文字列
	 */
	toString() {
		return "camera[\n" + "eye  :" + this.eye + ",\n" + "at   :" + this.at + ",\n" + "fovY :" + this.fovY + "]";
	}
}