Home Manual Reference Source

src/basic/S3System.js

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

import S3Camera from "./S3Camera.js";
import S3Light from "./S3Light.js";
import S3Material from "./S3Material.js";
import S3Mesh from "./S3Mesh.js";
import S3Model from "./S3Model.js";
import S3Scene from "./S3Scene.js";
import S3Texture from "./S3Texture.js";
import S3TriangleIndex from "./S3TriangleIndex.js";
import S3Vertex from "./S3Vertex.js";

/**
 * /////////////////////////////////////////////////////////
 * 描写に使用するシーンを構成するクラス群
 * 3DCGを作成するための行列を準備したり、シーンの描写をしたりする
 *
 * 3DCGを作るうえで必要最小限の機能を提供する
 * ・それらを構成する頂点、材質、面(全てimmutable)
 * ・モデル (mutable)
 * ・カメラ (mutable)
 * ・シーン (mutable)
 * ・描写用の行列作成
 * ・描写のための必要最低限の計算
 *
 * ポリゴン情報を構成部品
 * S3Vertex			頂点
 * S3Material		素材
 * S3TriangleIndex	インデックス
 * S3Mesh			頂点とインデックス情報と素材からなるメッシュ
 *
 * ポリゴンの描写用構成部品
 * S3Model			どの座標にどのように表示するかモデル
 * S3Camera			映像をどのように映すか
 * S3Scene			モデルとカメラを使用してシーン
 * S3Texture		テクスチャ
 * /////////////////////////////////////////////////////////
 */

/**
 * 3DCGシステム全体を管理するクラス
 *
 * 3DCGのための座標変換やシーン管理、基本的な生成処理・ユーティリティ関数などをまとめて提供します。
 * 頂点やメッシュ、マテリアルなど各種オブジェクトのファクトリ機能も持ちます。
 *
 * @class
 * @module S3
 *
 */
export default class S3System {
	/**
	 * S3Systemインスタンスを作成します。
	 * 描画モードや背景色などを初期化します。
	 */
	constructor() {
		/**
		 * 現在のシステムモード(OpenGL/DirectX)
		 * @type {number}
		 * @see S3System.SYSTEM_MODE
		 */
		this.systemmode = S3System.SYSTEM_MODE.OPEN_GL;

		/**
		 * 深度バッファのモード(OpenGL/DirectX)
		 * @type {number}
		 * @see S3System.DEPTH_MODE
		 */
		this.depthmode = S3System.DEPTH_MODE.OPEN_GL;

		/**
		 * 座標系モード(右手系/左手系)
		 * @type {number}
		 * @see S3System.DIMENSION_MODE
		 */
		this.dimensionmode = S3System.DIMENSION_MODE.RIGHT_HAND;

		/**
		 * ベクトル型のモード(VECTOR4x1 / VECTOR1x4)
		 * @type {number}
		 * @see S3System.VECTOR_MODE
		 */
		this.vectormode = S3System.VECTOR_MODE.VECTOR4x1;

		/**
		 * 前面判定の面の向き(時計回り/反時計回り)
		 * @type {number}
		 * @see S3System.FRONT_FACE
		 */
		this.frontface = S3System.FRONT_FACE.COUNTER_CLOCKWISE;

		/**
		 * カリングモード(面の非表示除去方法)
		 * @type {number}
		 * @see S3System.CULL_MODE
		 */
		this.cullmode = S3System.CULL_MODE.BACK;

		/**
		 * 背景色(RGBAベクトル)
		 * @type {S3Vector}
		 */
		this.backgroundColor = new S3Vector(1.0, 1.0, 1.0, 1.0);

		/**
		 * 描画に使うcanvas要素
		 * @type {HTMLCanvasElement}
		 */
		this.canvas = null;

		this._init();
	}

	/**
	 * 内部状態を初期化します(描画モードや背景色のリセット)。
	 * @private
	 */
	_init() {
		this.setSystemMode(S3System.SYSTEM_MODE.OPEN_GL);
		this.setBackgroundColor(new S3Vector(1.0, 1.0, 1.0, 1.0));
	}

	/**
	 * ユニークなID文字列を発行します(テクスチャなどの管理用途)。
	 * @returns {string} 新しいID文字列
	 */
	_createID() {
		if (!this._CREATE_ID) {
			/**
			 * 内部で一意なIDを発行するためのカウンタ配列
			 * @type {number[]} [4]
			 * @private
			 */
			this._CREATE_ID = [0, 0, 0, 0];
		}
		const id =
			this._CREATE_ID[3].toString(16) +
			":" +
			this._CREATE_ID[2].toString(16) +
			":" +
			this._CREATE_ID[1].toString(16) +
			":" +
			this._CREATE_ID[0].toString(16);
		this._CREATE_ID[0]++;
		for (let i = 0; i < 4; i++) {
			if (this._CREATE_ID[i] === 0x100000000) {
				this._CREATE_ID[i] = 0;
				if (i < 3) {
					this._CREATE_ID[i + 1]++;
				} else {
					// 全てのカウンタがオーバーフローした場合
					throw "createID";
				}
			} else {
				break;
			}
		}
		return id;
	}

	/**
	 * 画像やテキストファイルをダウンロードします。
	 * 画像拡張子ならImage要素、それ以外はテキストとして取得しコールバックします。
	 * @param {string} url 取得先URL
	 * @param {function} callback 取得完了時に呼ばれるコールバック関数
	 */
	_download(url, callback) {
		const dotlist = url.split(".");
		let isImage = false;
		const ext = "";
		if (dotlist.length > 1) {
			const ext = dotlist[dotlist.length - 1].toLocaleString();
			isImage =
				ext === "gif" || ext === "jpg" || ext === "png" || ext === "bmp" || ext === "svg" || ext === "jpeg";
		}
		if (isImage) {
			const image = new Image();
			image.onload = function () {
				callback(image, ext);
			};
			image.src = url;
			return;
		}
		const http = new XMLHttpRequest();
		/**
		 * @returns {void}
		 */
		const handleHttpResponse = function () {
			if (http.readyState === 4) {
				// DONE
				if (http.status !== 200) {
					console.log("error download [" + url + "]");
					return null;
				}
				callback(http.responseText, ext);
			}
		};
		http.onreadystatechange = handleHttpResponse;
		http.open("GET", url, true);
		http.send(null);
	}

	/**
	 * 任意の値をS3Vectorに変換します。
	 * @param {S3Vector|Array<number>|number} x 変換対象
	 * @returns {S3Vector} ベクトル化した値
	 */
	_toVector3(x) {
		if (x instanceof S3Vector) {
			return x;
		} else if (typeof x === "number") {
			return new S3Vector(x, x, x);
		} else if (x instanceof Array) {
			return new S3Vector(x[0], x[1], x[2]);
		} else {
			throw "IllegalArgumentException";
		}
	}

	/**
	 * 任意の値を数値に変換します。
	 * @param {*} x 変換対象
	 * @returns {number} 数値
	 */
	_toValue(x) {
		if (!isNaN(x)) {
			return x;
		} else {
			throw "IllegalArgumentException";
		}
	}

	/**
	 * 背景色を設定します。
	 * @param {S3Vector} color RGBAで指定する背景色
	 */
	setBackgroundColor(color) {
		/**
		 * 背景色(RGBAベクトル)
		 * @type {S3Vector}
		 */
		this.backgroundColor = color;
	}

	/**
	 * 背景色を取得します。
	 * @returns {S3Vector} 現在の背景色(RGBA)
	 */
	getBackgroundColor() {
		return this.backgroundColor;
	}

	/**
	 * システムモードを設定します(OpenGL/DIRECT_Xなど)。
	 * 各種描画パラメータも合わせて設定されます。
	 * @param {number} mode S3System.SYSTEM_MODE で定義される値
	 */
	setSystemMode(mode) {
		this.systemmode = mode;
		if (this.systemmode === S3System.SYSTEM_MODE.OPEN_GL) {
			this.depthmode = S3System.DEPTH_MODE.OPEN_GL;
			this.dimensionmode = S3System.DIMENSION_MODE.RIGHT_HAND;
			this.vectormode = S3System.VECTOR_MODE.VECTOR4x1;
			this.frontface = S3System.FRONT_FACE.COUNTER_CLOCKWISE;
			this.cullmode = S3System.CULL_MODE.BACK;
		} else {
			this.depthmode = S3System.DEPTH_MODE.DIRECT_X;
			this.dimensionmode = S3System.DIMENSION_MODE.LEFT_HAND;
			this.vectormode = S3System.VECTOR_MODE.VECTOR1x4;
			this.frontface = S3System.FRONT_FACE.CLOCKWISE;
			this.cullmode = S3System.CULL_MODE.BACK;
		}
	}

	/**
	 * 深度(Z値の扱い)のモードを設定します。
	 * @param {number} depthmode S3System.DEPTH_MODE
	 */
	setDepthMode(depthmode) {
		this.depthmode = depthmode;
	}

	/**
	 * 座標系(右手/左手系)を設定します。
	 * @param {number} dimensionmode S3System.DIMENSION_MODE
	 */
	setDimensionMode(dimensionmode) {
		this.dimensionmode = dimensionmode;
	}

	/**
	 * ベクトル表現のモードを設定します(縦型/横型)。
	 * @param {number} vectormode S3System.VECTOR_MODE
	 */
	setVectorMode(vectormode) {
		this.vectormode = vectormode;
	}

	/**
	 * 前面と判定する面の頂点順序(時計回り/反時計回り)を設定します。
	 * @param {number} frontface S3System.FRONT_FACE
	 */
	setFrontMode(frontface) {
		this.frontface = frontface;
	}

	/**
	 * カリング(非表示面除去)の方法を設定します。
	 * @param {number} cullmode S3System.CULL_MODE
	 */
	setCullMode(cullmode) {
		this.cullmode = cullmode;
	}

	/**
	 * 描画に使うCanvasを関連付け、2D描画用Contextを内部にセットします。
	 * @param {HTMLCanvasElement} canvas 使用するcanvas要素
	 */
	setCanvas(canvas) {
		const that = this;
		const ctx = canvas.getContext("2d");
		this.canvas = canvas;

		/**
		 * 2D描画用のユーティリティオブジェクト(drawLine, drawLinePolygonなど)
		 * @property {CanvasRenderingContext2D} context 2D描画コンテキスト
		 * @property {function(S3Vector, S3Vector):void} drawLine 2点間の直線を描画
		 * @property {function(S3Vector, S3Vector, S3Vector):void} drawLinePolygon 3点から三角形(線のみ)を描画
		 * @property {function(number):void} setLineWidth 線の太さを設定
		 * @property {function(string):void} setLineColor 線の色を設定
		 * @property {function():void} clear キャンバス全体を背景色で塗りつぶし
		 */
		this.context2d = {
			context: ctx,

			/**
			 * 2点間の直線を描画します。
			 * @param {S3Vector} v0 始点
			 * @param {S3Vector} v1 終点
			 */
			drawLine: function (v0, v1) {
				ctx.beginPath();
				ctx.moveTo(v0.x, v0.y);
				ctx.lineTo(v1.x, v1.y);
				ctx.stroke();
			},

			/**
			 * 3点で囲む三角形の外枠(線のみ)を描画します。
			 * @param {S3Vector} v0
			 * @param {S3Vector} v1
			 * @param {S3Vector} v2
			 */
			drawLinePolygon: function (v0, v1, v2) {
				ctx.beginPath();
				ctx.moveTo(v0.x, v0.y);
				ctx.lineTo(v1.x, v1.y);
				ctx.lineTo(v2.x, v2.y);
				ctx.closePath();
				ctx.stroke();
			},

			/**
			 * 線の太さを設定します(ピクセル単位)。
			 * @param {number} width 線幅
			 */
			setLineWidth: function (width) {
				ctx.lineWidth = width;
			},

			/**
			 * 線の色を設定します(CSSカラー形式)。
			 * @param {string} color 線の色(例: "rgb(255,0,0)")
			 */
			setLineColor: function (color) {
				ctx.strokeStyle = color;
			},

			/**
			 * キャンバス全体を背景色でクリアします。
			 */
			clear: function () {
				const color = that.getBackgroundColor();
				ctx.clearRect(0, 0, that.canvas.width, that.canvas.height);
				ctx.fillStyle =
					"rgba(" + color.x * 255 + "," + color.y * 255 + "," + color.z * 255 + "," + color.w + ")";
				ctx.fillRect(0, 0, that.canvas.width, that.canvas.height);
			}
		};
	}

	/**
	 * 三角形がカリング対象かどうか判定します。
	 * @param {S3Vector} p1 頂点1
	 * @param {S3Vector} p2 頂点2
	 * @param {S3Vector} p3 頂点3
	 * @returns {boolean} trueの場合は描画しない
	 */
	testCull(p1, p2, p3) {
		if (this.cullmode === S3System.CULL_MODE.NONE) {
			return false;
		}
		if (this.cullmode === S3System.CULL_MODE.FRONT_AND_BACK) {
			return true;
		}
		const isclock = S3Vector.isClockwise(p1, p2, p3);
		if (isclock === null) {
			return true;
		} else if (!isclock) {
			if (this.frontface === S3System.FRONT_FACE.CLOCKWISE) {
				return this.cullmode !== S3System.CULL_MODE.BACK;
			} else {
				return this.cullmode !== S3System.CULL_MODE.FRONT;
			}
		} else {
			if (this.frontface === S3System.FRONT_FACE.CLOCKWISE) {
				return this.cullmode === S3System.CULL_MODE.BACK;
			} else {
				return this.cullmode === S3System.CULL_MODE.FRONT;
			}
		}
	}

	/**
	 * ビューポート行列を生成します。
	 * @param {number} x 左上X
	 * @param {number} y 左上Y
	 * @param {number} Width 幅
	 * @param {number} Height 高さ
	 * @param {number} [MinZ=0.0] 最小深度
	 * @param {number} [MaxZ=1.0] 最大深度
	 * @returns {S3Matrix} ビューポート変換行列
	 */
	getMatrixViewport(x, y, Width, Height, MinZ, MaxZ) {
		if (MinZ === undefined) {
			MinZ = 0.0;
		}
		if (MaxZ === undefined) {
			MaxZ = 1.0;
		}
		// M.m11 は、DirectXだとマイナス、OpenGLだとプラスである
		// 今回は、下がプラスであるcanvasに表示させることを考えて、マイナスにしてある。
		const M = new S3Matrix();
		M.m00 = Width / 2;
		M.m01 = 0.0;
		M.m02 = 0.0;
		M.m03 = 0.0;
		M.m10 = 0.0;
		M.m11 = -Height / 2;
		M.m12 = 0.0;
		M.m13 = 0.0;
		M.m20 = 0.0;
		M.m21 = 0.0;
		M.m22 = 1.0;
		M.m23 = 1.0;
		M.m30 = x + Width / 2;
		M.m31 = y + Height / 2;
		M.m32 = 0.0;
		M.m33 = 1.0;

		if (this.depthmode === S3System.DEPTH_MODE.DIRECT_X) {
			M.m22 = MinZ - MaxZ;
			M.m32 = MinZ;
		} else if (this.depthmode === S3System.DEPTH_MODE.OPEN_GL) {
			M.m22 = (MinZ - MaxZ) / 2;
			M.m32 = (MinZ + MaxZ) / 2;
		}
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? M.transposed() : M;
	}

	/**
	 * 視体積の上下方向の視野角を求めます。
	 * @param {number} zoomY
	 * @returns {number}
	 */
	static calcFovY(zoomY) {
		return 2.0 * Math.atan(1.0 / zoomY);
	}

	/**
	 * アスペクト比を計算します。
	 * @static
	 * @param {number} width 幅
	 * @param {number} height 高さ
	 * @returns {number} アスペクト比
	 */
	static calcAspect(width, height) {
		return width / height;
	}

	/**
	 * 視野角(FOVY)から射影行列を生成します。
	 * @param {number} fovY 視体積の上下方向の視野角(0度から180度)
	 * @param {number} Aspect 近平面、遠平面のアスペクト比(Width / Height)
	 * @param {number} Near カメラから近平面までの距離(ニアークリッピング平面)
	 * @param {number} Far カメラから遠平面までの距離(ファークリッピング平面)
	 * @returns {S3Matrix} 射影変換行列
	 */
	getMatrixPerspectiveFov(fovY, Aspect, Near, Far) {
		const arc = S3Math.radius(fovY);
		const zoomY = 1.0 / Math.tan(arc / 2.0);
		const zoomX = zoomY / Aspect;
		const M = new S3Matrix();
		M.m00 = zoomX;
		M.m01 = 0.0;
		M.m02 = 0.0;
		M.m03 = 0.0;
		M.m10 = 0.0;
		M.m11 = zoomY;
		M.m12 = 0.0;
		M.m13 = 0.0;
		M.m20 = 0.0;
		M.m21 = 0.0;
		M.m22 = 1.0;
		M.m23 = 1.0;
		M.m30 = 0.0;
		M.m31 = 0.0;
		M.m32 = 0.0;
		M.m33 = 0.0;
		const Delta = Far - Near;
		if (Near > Far) {
			throw "Near > Far error";
		} else if (Delta === 0.0) {
			throw "divide error";
		}
		if (this.depthmode === S3System.DEPTH_MODE.DIRECT_X) {
			M.m22 = Far / Delta;
			M.m32 = (-Far * Near) / Delta;
		} else if (this.depthmode === S3System.DEPTH_MODE.OPEN_GL) {
			M.m22 = (Far + Near) / Delta;
			M.m32 = (-2.0 * Far * Near) / Delta;
		}
		if (this.dimensionmode === S3System.DIMENSION_MODE.RIGHT_HAND) {
			M.m22 = -M.m22;
			M.m23 = -M.m23;
		}
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? M.transposed() : M;
	}

	/**
	 * カメラのビュー行列を生成します。
	 * @param {S3Vector} eye カメラの座標の位置ベクトル
	 * @param {S3Vector} at カメラの注視点の位置ベクトル
	 * @param {S3Vector} [up] カメラの上への方向ベクトル
	 * @returns {S3Matrix} ビュー行列
	 */
	getMatrixLookAt(eye, at, up) {
		if (up === undefined) {
			up = new S3Vector(0.0, 1.0, 0.0);
		}
		// Z ベクトルの作成
		let Z = eye.getDirectionNormalized(at);
		if (this.dimensionmode === S3System.DIMENSION_MODE.RIGHT_HAND) {
			// 右手系なら反転
			Z = Z.negate();
		}
		// X, Y ベクトルの作成
		const X = up.cross(Z).normalize();
		const Y = Z.cross(X);
		const M = new S3Matrix();
		M.m00 = X.x;
		M.m01 = Y.x;
		M.m02 = Z.x;
		M.m03 = 0.0;
		M.m10 = X.y;
		M.m11 = Y.y;
		M.m12 = Z.y;
		M.m13 = 0.0;
		M.m20 = X.z;
		M.m21 = Y.z;
		M.m22 = Z.z;
		M.m23 = 0.0;
		M.m30 = -X.dot(eye);
		M.m31 = -Y.dot(eye);
		M.m32 = -Z.dot(eye);
		M.m33 = 1.0;
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? M.transposed() : M;
	}

	/**
	 * 単位行列を生成します。
	 * @returns {S3Matrix} 単位行列
	 */
	getMatrixIdentity() {
		const M = new S3Matrix();
		M.m00 = 1.0;
		M.m01 = 0.0;
		M.m02 = 0.0;
		M.m03 = 0.0;
		M.m10 = 0.0;
		M.m11 = 1.0;
		M.m12 = 0.0;
		M.m13 = 0.0;
		M.m20 = 0.0;
		M.m21 = 0.0;
		M.m22 = 1.0;
		M.m23 = 0.0;
		M.m30 = 0.0;
		M.m31 = 0.0;
		M.m32 = 0.0;
		M.m33 = 1.0;
		return M;
	}

	/**
	 * 平行移動行列を生成します。
	 * @param {number} x X移動量
	 * @param {number} y Y移動量
	 * @param {number} z Z移動量
	 * @returns {S3Matrix} 平行移動行列
	 */
	getMatrixTranslate(x, y, z) {
		const M = new S3Matrix();
		M.m00 = 1.0;
		M.m01 = 0.0;
		M.m02 = 0.0;
		M.m03 = 0.0;
		M.m10 = 0.0;
		M.m11 = 1.0;
		M.m12 = 0.0;
		M.m13 = 0.0;
		M.m20 = 0.0;
		M.m21 = 0.0;
		M.m22 = 1.0;
		M.m23 = 0.0;
		M.m30 = x;
		M.m31 = y;
		M.m32 = z;
		M.m33 = 1.0;
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? M.transposed() : M;
	}

	/**
	 * 拡大縮小行列を生成します。
	 * @param {number} x X方向スケール
	 * @param {number} y Y方向スケール
	 * @param {number} z Z方向スケール
	 * @returns {S3Matrix} スケーリング行列
	 */
	getMatrixScale(x, y, z) {
		const M = new S3Matrix();
		M.m00 = x;
		M.m01 = 0.0;
		M.m02 = 0.0;
		M.m03 = 0.0;
		M.m10 = 0.0;
		M.m11 = y;
		M.m12 = 0.0;
		M.m13 = 0.0;
		M.m20 = 0.0;
		M.m21 = 0.0;
		M.m22 = z;
		M.m23 = 0.0;
		M.m30 = 0.0;
		M.m31 = 0.0;
		M.m32 = 0.0;
		M.m33 = 1.0;
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? M.transposed() : M;
	}

	/**
	 * X軸周りの回転行列を生成します。
	 * @param {number} degree 角度(度)
	 * @returns {S3Matrix} 回転行列
	 */
	getMatrixRotateX(degree) {
		const arc = S3Math.radius(degree);
		const cos = Math.cos(arc);
		const sin = Math.sin(arc);
		const M = new S3Matrix();
		M.m00 = 1.0;
		M.m01 = 0.0;
		M.m02 = 0.0;
		M.m03 = 0.0;
		M.m10 = 0.0;
		M.m11 = cos;
		M.m12 = sin;
		M.m13 = 0.0;
		M.m20 = 0.0;
		M.m21 = -sin;
		M.m22 = cos;
		M.m23 = 0.0;
		M.m30 = 0.0;
		M.m31 = 0.0;
		M.m32 = 0.0;
		M.m33 = 1.0;
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? M.transposed() : M;
	}

	/**
	 * Y軸周りの回転行列を生成します。
	 * @param {number} degree 角度(度)
	 * @returns {S3Matrix} 回転行列
	 */
	getMatrixRotateY(degree) {
		const arc = S3Math.radius(degree);
		const cos = Math.cos(arc);
		const sin = Math.sin(arc);
		const M = new S3Matrix();
		M.m00 = cos;
		M.m01 = 0.0;
		M.m02 = -sin;
		M.m03 = 0.0;
		M.m10 = 0.0;
		M.m11 = 1.0;
		M.m12 = 0.0;
		M.m13 = 0.0;
		M.m20 = sin;
		M.m21 = 0.0;
		M.m22 = cos;
		M.m23 = 0.0;
		M.m30 = 0.0;
		M.m31 = 0.0;
		M.m32 = 0.0;
		M.m33 = 1.0;
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? M.transposed() : M;
	}

	/**
	 * Z軸周りの回転行列を生成します。
	 * @param {number} degree 角度(度)
	 * @returns {S3Matrix} 回転行列
	 */
	getMatrixRotateZ(degree) {
		const arc = S3Math.radius(degree);
		const cos = Math.cos(arc);
		const sin = Math.sin(arc);
		const M = new S3Matrix();
		M.m00 = cos;
		M.m01 = sin;
		M.m02 = 0.0;
		M.m03 = 0.0;
		M.m10 = -sin;
		M.m11 = cos;
		M.m12 = 0.0;
		M.m13 = 0.0;
		M.m20 = 0.0;
		M.m21 = 0.0;
		M.m22 = 1.0;
		M.m23 = 0.0;
		M.m30 = 0.0;
		M.m31 = 0.0;
		M.m32 = 0.0;
		M.m33 = 1.0;
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? M.transposed() : M;
	}

	/**
	 * 縦型/横型を踏まえて2つの行列を掛けます。
	 * @param {S3Matrix} A
	 * @param {S3Matrix} B
	 * @returns {S3Matrix} 計算結果
	 */
	mulMatrix(A, B) {
		// 横型の場合は、v[AB]=u
		// 縦型の場合は、[BA]v=u
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? B.mulMatrix(A) : A.mulMatrix(B);
	}

	/**
	 * 縦型/横型を踏まえて行列とベクトルを掛けます。
	 * @param {S3Matrix} A
	 * @param {S3Vector} B
	 * @returns {S3Vector} 計算結果
	 */
	mulVector(A, B) {
		// 横型の場合は、[vA]=u
		// 縦型の場合は、[Av]=u
		return this.vectormode === S3System.VECTOR_MODE.VECTOR4x1 ? A.mulVector(B) : B.mul(A);
	}

	/**
	 * 航空機の姿勢制御 ZXY(ロール・ピッチ・ヨー)の順で回転行列を作成します。
	 * @param {number} z ロール角(Z)
	 * @param {number} x ピッチ角(X)
	 * @param {number} y ヨー角(Y)
	 * @returns {S3Matrix} 合成回転行列
	 */
	getMatrixRotateZXY(z, x, y) {
		const Z = this.getMatrixRotateZ(z);
		const X = this.getMatrixRotateX(x);
		const Y = this.getMatrixRotateY(y);
		return this.mulMatrix(this.mulMatrix(Z, X), Y);
	}

	/**
	 * 指定モデルのワールド変換行列を生成します(スケール→回転→移動の順)。
	 * @param {S3Model} model 対象モデル
	 * @returns {S3Matrix} ワールド変換行列
	 */
	getMatrixWorldTransform(model) {
		// 回転行列
		const R = this.getMatrixRotateZXY(model.angles.roll, model.angles.pitch, model.angles.yaw);
		// スケーリング
		const S = this.getMatrixScale(model.scale.x, model.scale.y, model.scale.z);
		// 移動行列
		const T = this.getMatrixTranslate(model.position.x, model.position.y, model.position.z);
		// ワールド変換行列を作成する
		const W = this.mulMatrix(this.mulMatrix(S, R), T);
		return W;
	}

	/**
	 * 2Dキャンバスの内容をクリアします。
	 */
	clear() {
		this.context2d.clear();
	}

	/**
	 * 頂点リストをMVP変換・射影して新しい頂点配列を返します。
	 * @param {Array<S3Vertex>} vertexlist 変換対象の頂点配列
	 * @param {S3Matrix} MVP モデル・ビュー・射影行列
	 * @param {S3Matrix} Viewport ビューポート変換行列
	 * @returns {Array<S3Vertex>} 変換後の頂点配列
	 */
	_calcVertexTransformation(vertexlist, MVP, Viewport) {
		const newvertexlist = [];

		for (let i = 0; i < vertexlist.length; i++) {
			let p = vertexlist[i].position;

			//	console.log("1 " + p);
			//	console.log("2 " + this.mulMatrix(VPS.LookAt, p));
			//	console.log("3 " + this.mulMatrix(VPS.PerspectiveFov, this.mulMatrix(VPS.LookAt, p)));
			//	console.log("4 " + this.mulMatrix(MVP, p));

			p = this.mulVector(MVP, p);
			const rhw = p.w;
			p = p.mul(1.0 / rhw);
			p = this.mulVector(Viewport, p);
			newvertexlist[i] = new S3Vertex(p);
		}
		return newvertexlist;
	}

	/**
	 * X, Y, Zの座標軸を描画します(デバッグ用)。
	 * @param {S3Scene} scene 対象シーン
	 */
	drawAxis(scene) {
		const VPS = scene.getCamera().getVPSMatrix(this.canvas);

		const vertexvector = [];
		vertexvector[0] = new S3Vector(0, 0, 0);
		vertexvector[1] = new S3Vector(10, 0, 0);
		vertexvector[2] = new S3Vector(0, 10, 0);
		vertexvector[3] = new S3Vector(0, 0, 10);

		const newvector = [];
		const M = this.mulMatrix(VPS.LookAt, VPS.PerspectiveFov);
		for (let i = 0; i < vertexvector.length; i++) {
			let p = vertexvector[i];
			p = this.mulVector(M, p);
			p = p.mul(1.0 / p.w);
			p = this.mulVector(VPS.Viewport, p);
			newvector[i] = p;
		}

		this.context2d.setLineWidth(3.0);
		this.context2d.setLineColor("rgb(255, 0, 0)");
		this.context2d.drawLine(newvector[0], newvector[1]);
		this.context2d.setLineColor("rgb(0, 255, 0)");
		this.context2d.drawLine(newvector[0], newvector[2]);
		this.context2d.setLineColor("rgb(0, 0, 255)");
		this.context2d.drawLine(newvector[0], newvector[3]);
	}

	/**
	 * ポリゴン(三角形群)を描画します(ラインで表示)。
	 * @param {Array<S3Vertex>} vetexlist 頂点配列
	 * @param {Array<S3TriangleIndex>} triangleindexlist インデックス配列
	 */
	_drawPolygon(vetexlist, triangleindexlist) {
		for (let i = 0; i < triangleindexlist.length; i++) {
			const ti = triangleindexlist[i];
			if (
				this.testCull(
					vetexlist[ti.index[0]].position,
					vetexlist[ti.index[1]].position,
					vetexlist[ti.index[2]].position
				)
			) {
				continue;
			}
			this.context2d.drawLinePolygon(
				vetexlist[ti.index[0]].position,
				vetexlist[ti.index[1]].position,
				vetexlist[ti.index[2]].position
			);
		}
	}

	/**
	 * シーン全体を描画します。
	 * @param {S3Scene} scene 描画対象のシーン
	 */
	drawScene(scene) {
		const VPS = scene.getCamera().getVPSMatrix(this.canvas);

		this.context2d.setLineWidth(1.0);
		this.context2d.setLineColor("rgb(0, 0, 0)");

		const models = scene.getModels();
		for (let i = 0; i < models.length; i++) {
			const model = models[i];
			const mesh = model.getMesh();
			if (mesh.isComplete() === false) {
				continue;
			}
			const M = this.getMatrixWorldTransform(model);
			const MVP = this.mulMatrix(this.mulMatrix(M, VPS.LookAt), VPS.PerspectiveFov);
			const vlist = this._calcVertexTransformation(mesh.src.vertex, MVP, VPS.Viewport);
			this._drawPolygon(vlist, mesh.src.triangleindex);
		}
	}

	/**
	 * 不要になったリソースを解放します(未実装)。
	 * @param {Object} obj 解放対象のオブジェクト
	 * @returns {void}
	 */
	_disposeObject(obj) {}

	/**
	 * 新しい頂点インスタンスを生成します。
	 * @param {S3Vector} position 頂点座標
	 * @returns {S3Vertex} 生成された頂点
	 */
	createVertex(position) {
		return new S3Vertex(position);
	}

	/**
	 * 新しい三角形インデックスインスタンスを生成します。
	 * @param {number} i1 頂点1のインデックス
	 * @param {number} i2 頂点2のインデックス
	 * @param {number} i3 頂点3のインデックス
	 * @param {Array<number>} indexlist 頂点インデックス配列
	 * @param {number} [materialIndex] マテリアルインデックス
	 * @param {Array<S3Vector>} [uvlist] UV座標配列
	 * @returns {S3TriangleIndex} 生成された三角形インデックス
	 */
	createTriangleIndex(i1, i2, i3, indexlist, materialIndex, uvlist) {
		return new S3TriangleIndex(i1, i2, i3, indexlist, materialIndex, uvlist);
	}

	/**
	 * 新しいテクスチャインスタンスを生成します。
	 * @param {string|ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} [name] テクスチャ名や画像データ
	 * @returns {S3Texture} 生成されたテクスチャ
	 */
	createTexture(name) {
		return new S3Texture(this, name);
	}

	/**
	 * 新しいシーンインスタンスを生成します。
	 * @returns {S3Scene} 生成されたシーン
	 */
	createScene() {
		return new S3Scene();
	}

	/**
	 * 新しいモデルインスタンスを生成します。
	 * @returns {S3Model} 生成されたモデル
	 */
	createModel() {
		return new S3Model();
	}

	/**
	 * 新しいメッシュインスタンスを生成します。
	 * @returns {S3Mesh} 生成されたメッシュ
	 */
	createMesh() {
		return new S3Mesh(this);
	}

	/**
	 * 新しいマテリアルインスタンスを生成します。
	 * @param {string} [name] マテリアル名
	 * @returns {S3Material} 生成されたマテリアル
	 */
	createMaterial(name) {
		return new S3Material(this, name);
	}

	/**
	 * 新しいライトインスタンスを生成します。
	 * @returns {S3Light} 生成されたライト
	 */
	createLight() {
		return new S3Light();
	}

	/**
	 * 新しいカメラインスタンスを生成します。
	 * @returns {S3Camera} 生成されたカメラ
	 */
	createCamera() {
		const camera = new S3Camera(this);
		return camera;
	}
}

/**
 * システムの描画モードを指定する定数
 *
 * - OPEN_GL: OpenGLに準拠した描画処理・座標変換方式を用います。
 * - DIRECT_X: DirectXに準拠した描画処理・座標変換方式を用います。
 *
 * シーンの座標系や深度バッファの扱いなどにも影響します。
 * @enum {number}
 * @property {number} OPEN_GL   OpenGL準拠(値: 0)
 * @property {number} DIRECT_X  DirectX準拠(値: 1)
 */
S3System.SYSTEM_MODE = {
	/** OpenGL準拠の描画方式(右手系、Zバッファ0~1など) */
	OPEN_GL: 0,
	/** DirectX準拠の描画方式(左手系、Zバッファ0~1など) */
	DIRECT_X: 1
};

S3System.DEPTH_MODE = {
	/**
	 * Z値の範囲などの依存関係をOpenGL準拠
	 * @type Number
	 */
	OPEN_GL: 0,
	/**
	 * Z値の範囲などの依存関係をDirecX準拠
	 * @type Number
	 */
	DIRECT_X: 1
};

S3System.DIMENSION_MODE = {
	/**
	 * 右手系
	 * @type Number
	 */
	RIGHT_HAND: 0,
	/**
	 * 左手系
	 * @type Number
	 */
	LEFT_HAND: 1
};

S3System.VECTOR_MODE = {
	/**
	 * 値を保持するベクトルを縦ベクトルとみなす
	 * @type Number
	 */
	VECTOR4x1: 0,
	/**
	 * 値を保持するベクトルを横ベクトルとみなす
	 * @type Number
	 */
	VECTOR1x4: 1
};

S3System.FRONT_FACE = {
	/**
	 * 反時計回りを前面とする
	 * @type Number
	 */
	COUNTER_CLOCKWISE: 0,

	/**
	 * 時計回りを前面とする
	 * @type Number
	 */
	CLOCKWISE: 1
};

S3System.CULL_MODE = {
	/**
	 * 常にすべての三角形を描画します。
	 * @type Number
	 */
	NONE: 0,

	/**
	 * 前向きの三角形を描写しません。
	 * @type Number
	 */
	FRONT: 1,

	/**
	 * 後ろ向きの三角形を描写しません。
	 * @type Number
	 */
	BACK: 2,

	/**
	 * 常に描写しない。
	 * @type Number
	 */
	FRONT_AND_BACK: 3
};