前回の「3Dを基礎から勉強する テクスチャマッピング」では、テスクチャを貼り付けたことにより陰影が消えてしまいました。
陰影(シェーディング)については、以前に「3Dを基礎から勉強する フラットシェーディング」をやりました。これを応用すれば、陰影はつけれそうです。
今のプログラムはもともと「3Dモデルを表示するJavaアプレットの作成」を参考にTypeScriptに移植したものです。この記事内の「【ステップ4】面の明るさを設定する」でもフラットシェーディングを行っていますので、この考え方で実現させます。
現在、面を塗る際に法線ベクトルのz成分を元に陰影の描画色を定めています。
var rgb = this.HSVtoRGB(0.4 * 360, 0.5 * 255, face.nz * 255);
g.fillStyle = 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'; // 面の塗りつぶし
g.fill();
face.nzには1.0~0.0の値が入っており、光源の角度(cos)が「0°」に近くなると値が1.0となり面の明るさが最大値となります。
HTML5のCanvasでは、ピクセルデータを読み込んで色を演算させることが出来るのですが、今回はもっと簡単な方法を用います。
Canvasに透明度(globalAlpha)のプロパティがあり、その値は0.0(完全に透明)から 1.0(透明度なし)となっています。
ポリゴン面を黒色で塗る時にglobalAlphaプロパティを使ってテクスチャに陰影を付けます。
この際に、face.nzとglobalAlphaプロパティの値は反対関係にありますから、g.globalAlpha = 1 - face.nzとしています。
if (this.isTexture) {
// テクスチャの描画
var vertex_list = [pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y];
var uv_list = [face.coords[0].u, face.coords[0].v, face.coords[1].u, face.coords[1].v, face.coords[2].u, face.coords[2].v];
this.drawTriangle(g, this.texture, vertex_list, uv_list, i);
g.save();
g.globalAlpha = 1 - face.nz;
g.fillStyle = 'Black';
g.fill();
g.restore();
}
結果は下記の通りになります。
参考:床井研究室 コンピュータグラフィックス
10.陰影付け、12.テクスチャマッピング
TypeScriptですが、ソースコードを載せておきます。
// Class M22
class M22 {
_11: number = 1;
_12: number = 0;
_21: number = 0;
_22: number = 1;
constructor() {
this._11 = 1;
this._12 = 0;
this._21 = 0;
this._22 = 1;
}
// 逆行列
getInvert() {
var out = new M22();
var det = this._11 * this._22 - this._12 * this._21;
if (det > -0.0001 && det < 0.0001)
return null;
out._11 = this._22 / det;
out._22 = this._11 / det;
out._12 = -this._12 / det;
out._21 = -this._21 / det;
return out;
}
}
// Class Point
class Point {
constructor(public x: number, public y: number) { }
}
// Class UV
class UV {
constructor(public u: number, public v: number) { }
}
// 頂点クラス
class Vertex {
rx: number = 0;
ry: number = 0;
rz: number = 0;
screenX: number = 0;
screenY: number = 0;
constructor(public x: number, public y: number, public z: number) { }
}
// 面クラス
class Face {
v = []; // 面を構成する3つの頂点
z: number = 0; // 奥行き
nx: number = 0; // 法線
ny: number = 0;
nz: number = 0;
color: string = "#000000";
coords = [];
constructor(v0: Vertex, v1: Vertex, v2: Vertex, c: string, uv0: UV, uv1: UV, uv2: UV) {
this.v.push(v0);
this.v.push(v1);
this.v.push(v2);
this.color = c;
this.coords.push(uv0);
this.coords.push(uv1);
this.coords.push(uv2);
}
}
// 平面クラス
class Plane {
p = [];
vertices = [];
constructor(public center: Point, public scale: number, public style: string) { }
// 頂点のスクリーン座標を更新する
setScreenPosition(theta, phi) {
this.p = [];
for (var i: number = 0; i < this.vertices.length; i++) {
var v: Vertex = <Vertex>this.vertices[i];
// 回転後の座標値の算出
v.rx = v.x * Math.cos(theta) + v.z * Math.sin(theta);
v.ry = v.x * Math.sin(phi) * Math.sin(theta) + v.y * Math.cos(phi) - v.z * Math.sin(phi) * Math.cos(theta);
v.rz = - v.x * Math.cos(phi) * Math.sin(theta) + v.y * Math.sin(phi) + v.z * Math.cos(phi) * Math.cos(theta);
// スクリーン座標の算出
v.screenX = this.center.x + this.scale * v.rx;
v.screenY = this.center.y - this.scale * v.ry;
// 回転後の各頂点の座標を計算
this.p.push(new Point(v.screenX, v.screenY));
}
}
// 描画処理
draw(g: CanvasRenderingContext2D, p1: Point, p2: Point) {
g.beginPath();
g.lineWidth = 0.5;
g.moveTo(p1.x, p1.y);
g.lineTo(p2.x, p2.y);
g.closePath();
g.strokeStyle = this.style;
g.stroke();
}
}
// 軸クラス
class Axis extends Plane {
constructor(center: Point, scale: number, style: string, ax: string) {
super(center, scale, style);
var vertice = [];
// 頂点
switch (ax) {
case 'x':
vertice = [[-1, 0, 0], [1, 0, 0], [1, 0, 0], [-1, 0, 0]];
break;
case 'y':
vertice = [[0, -1, 0], [0, 1, 0], [0, 1, 0], [0, -1, 0]];
break;
case 'z':
vertice = [[0, 0, -1], [0, 0, 1], [0, 0, 1], [0, 0, -1]];
break;
}
for (var i: number = 0; i < vertice.length; i++) {
var v: Vertex = new Vertex(vertice[i][0], vertice[i][1], vertice[i][2]);
this.vertices.push(v);
}
}
// 軸描画処理
draw(g: CanvasRenderingContext2D) {
super.draw(g, this.p[0], this.p[1]);
}
}
// 立方体軸クラス
class AxisCube extends Plane {
constructor(center: Point, scale: number, style: string) {
super(center, scale, style);
var diff = (f: boolean) => { return f ? 1 : -1; }
// 立方体の頂点8つを作成する
//i x y z
//0 1 1 1
//1 -1 1 1
//2 -1 -1 1
//3 1 -1 1
//4 1 1 -1
//5 -1 1 -1
//6 -1 -1 -1
//7 1 -1 -1
for (var i: number = 0; i < 8; i++) {
var v: Vertex = new Vertex(diff(i % 4 % 3 == 0), diff(i % 4 < 2), diff(i < 4));
this.vertices.push(v);
}
}
draw(g: CanvasRenderingContext2D) {
// 頂点の間を線で結ぶ
for (var i: number = 0; i < 4; i++) {
super.draw(g, this.p[i], this.p[i + 4]);
super.draw(g, this.p[i], this.p[(i + 1) % 4]);
super.draw(g, this.p[i + 4], this.p[(i + 1) % 4 + 4]);
}
}
}
// インターフェイスの定義
class ModelData {
vertices: number[][];
faces: number[];
colors: string[];
uvs: UV[];
}
// モデルオブジェクトクラス
class ModelObject extends Plane {
private faces = []; // 面(三角形)列を保持する
private modelData: ModelData;
private hasColor: Boolean;
private hasUvs: Boolean;
animeLength: number;
isWireFrame: Boolean = true;
isFill: Boolean = true;
isTexture: Boolean = true;
isColorful: Boolean = true;
isCulling: Boolean = true;
texture: HTMLImageElement;
mousePoint: Point;
faceNo: number;
constructor(center: Point, scale: number, style: string) {
super(center, scale, style);
}
// モデルデータの生成
createModel(modelObject: any) {
this.modelData = new ModelData();
this.modelData.vertices = new Array();
// 頂点データ
if (modelObject.morphTargets !== undefined && modelObject.morphTargets.length != 0) {
// アニメーション分
this.animeLength = modelObject.morphTargets.length;
for (var j = 0; j < this.animeLength; j++) {
this.modelData.vertices[j] = new Array();
for (var i = 0; i < modelObject.morphTargets[j].vertices.length; i++) {
this.modelData.vertices[j].push(modelObject.morphTargets[j].vertices[i]);
}
}
}
else {
// アニメーションなし
this.animeLength = 1;
this.modelData.vertices[0] = new Array();
for (var i = 0; i < modelObject.vertices.length; i++) {
this.modelData.vertices[0].push(modelObject.vertices[i]);
}
}
// 面データ
this.modelData.faces = new Array();
for (var i = 0; i < modelObject.faces.length; i++) {
this.modelData.faces.push(modelObject.faces[i]);
}
// 色データ
this.hasColor = false;
if (modelObject.morphColors !== undefined) {
this.hasColor = true;
this.modelData.colors = new Array();
for (var i = 0; i < modelObject.morphColors[0].colors.length; i+=3) {
var r: number = modelObject.morphColors[0].colors[i + 0];
var g: number = modelObject.morphColors[0].colors[i + 1];
var b: number = modelObject.morphColors[0].colors[i + 2];
this.modelData.colors.push(this.getColorHexString(r,g,b));
}
}
// UVデータ
this.hasUvs = false;
if (modelObject.uvs !== undefined) {
this.hasUvs = true;
this.modelData.uvs = new Array();
for (i = 0; i < modelObject.uvs[0].length; i += 2) {
var u = modelObject.uvs[0][i + 0] % 1.0;
var v = modelObject.uvs[0][i + 1] % 1.0;
u = (u < 0) ? 1 + u : u;
v = (v < 0) ? v * -1 : 1 - v;
this.modelData.uvs.push(new UV(u, v));
}
}
}
uv(value) {
}
// ビットチェック
isBitSet(value, position) {
return value & (1 << position);
}
// モデルデータの設定
setModelData(idx:number) {
this.vertices = []; // 頂点列を初期化
this.faces = []; // 面列を初期化
var minV = new Vertex(10000, 10000, 10000);
var maxV = new Vertex(-10000, -10000, -10000);
var models = [];
// トークンごとの読み込み
for (var i = 0; i < this.modelData.vertices[idx].length; i += 3) {
var x = this.modelData.vertices[idx][i + 0];
var y = this.modelData.vertices[idx][i + 1];
var z = this.modelData.vertices[idx][i + 2];
// モデルサイズを更新
minV.x = Math.min(minV.x, x);
minV.y = Math.min(minV.y, y);
minV.z = Math.min(minV.z, z);
maxV.x = Math.max(maxV.x, x);
maxV.y = Math.max(maxV.y, y);
maxV.z = Math.max(maxV.z, z);
// 頂点列に新しい頂点を追加
this.vertices.push(new Vertex(x, y, z));
}
// THREE.JSONLoader.createModel の超簡易版
var j = 0;
for (var i = 0; i < this.modelData.faces.length; i += 11) {
// 先頭データはType(42=00101010) 例 42,v0,v1,v2,0,uv0,uv1,uv2,fv0,fv1,fv2
if (this.modelData.faces[i] != 42) {
continue;
}
// 頂点インデックスの取得 3頂点のみ対応
var v0 = this.modelData.faces[i + 1];
var v1 = this.modelData.faces[i + 2];
var v2 = this.modelData.faces[i + 3];
// 面列に新しい面を追加
var color: string = (this.hasColor) ? this.modelData.colors[j++] : "000000";
// UV情報
var uv0: UV = (this.hasUvs) ? this.modelData.uvs[this.modelData.faces[i + 5]] : new UV(0, 0);
var uv1: UV = (this.hasUvs) ? this.modelData.uvs[this.modelData.faces[i + 6]] : new UV(0, 0);
var uv2: UV = (this.hasUvs) ? this.modelData.uvs[this.modelData.faces[i + 7]] : new UV(0, 0);
// 面情報設定
this.faces.push(new Face(this.vertices[v0], this.vertices[v1], this.vertices[v2], color, uv0, uv1, uv2));
}
var modelSize = Math.max(maxV.x - minV.x, maxV.y - minV.y);
modelSize = Math.max(modelSize, maxV.z - minV.z);
// モデルの大きさが原点を中心とする1辺が2の立方体に収まるようにする
for (var i = 0; i < this.vertices.length; i++) {
var v = this.vertices[i];
v.x = (v.x - (minV.x + maxV.x) / 2) / modelSize * 2;
v.y = (v.y - (minV.y + maxV.y) / 2) / modelSize * 2;
v.z = (v.z - (minV.z + maxV.z) / 2) / modelSize * 2;
}
}
// 頂点のスクリーン座標を更新する
setScreenPosition(theta: number, phi: number) {
super.setScreenPosition(theta, phi);
for (var i: number = 0; i < this.faces.length; i++) {
var face = this.faces[i];
// 面の奥行き座標を更新
face.z = 0.0;
for (var j: number = 0; j < 3; j++) {
face.z += face.v[j].rz;
}
// 2辺のベクトルを計算
var v1_v0_x = face.v[1].rx - face.v[0].rx;
var v1_v0_y = face.v[1].ry - face.v[0].ry;
var v1_v0_z = face.v[1].rz - face.v[0].rz;
var v2_v0_x = face.v[2].rx - face.v[0].rx;
var v2_v0_y = face.v[2].ry - face.v[0].ry;
var v2_v0_z = face.v[2].rz - face.v[0].rz;
// 法線ベクトルを外積から求める
face.nx = v1_v0_y * v2_v0_z - v1_v0_z * v2_v0_y;
face.ny = v1_v0_z * v2_v0_x - v1_v0_x * v2_v0_z;
face.nz = v1_v0_x * v2_v0_y - v1_v0_y * v2_v0_x;
// 法線ベクトルの正規化
var l: number = Math.sqrt(face.nx * face.nx + face.ny * face.ny + face.nz * face.nz);
face.nx /= l;
face.ny /= l;
face.nz /= l;
}
// 面を奥行き座標で並び替える
this.faces.sort(function (a, b) {
return a["z"] - b["z"];
});
}
//3点が時計回りかどうかを調べる
//時計回りなら1,反時計回りで-1、直線で0を返す。
isFace(p1: Point, p2: Point, p3: Point) {
var result: number = 0;
var dx2: number;
var dy2: number;
var dx3: number;
var dy3: number;
dx2 = p2.x - p1.x;
dy2 = p2.y - p1.y;
dx3 = p3.x - p1.x;
dy3 = p3.y - p1.y;
if ((dx2 * dy3) > (dx3 * dy2)) result = -1;
else if ((dx2 * dy3) < (dx3 * dy2)) result = 1;
return result;
}
// モデル描画
draw(g: CanvasRenderingContext2D) {
// 三角形描画のための座標値を格納する配列
var px = [];
var py = [];
// 各面の描画
for (var i: number = 0; i < this.faces.length; i++) {
var face = this.faces[i];
var pt1: Point = new Point(face.v[0].screenX, face.v[0].screenY);
var pt2: Point = new Point(face.v[1].screenX, face.v[1].screenY);
var pt3: Point = new Point(face.v[2].screenX, face.v[2].screenY);
// カリング(隠面は描画しない)
if (this.isCulling) {
// 裏表を三角形頂点の配置順序で判定(時計回り以外なら描画しない)
if (this.isFace(pt1, pt2, pt3) <= 0) continue;
// 裏表を法線ベクトルで判定
//if (face.nz < 0) continue;
}
// 面の輪郭線の描画(三角形)
g.beginPath();
g.strokeStyle = 'black';
g.lineWidth = 1;
g.moveTo(pt1.x, pt1.y);
g.lineTo(pt2.x, pt2.y);
g.lineTo(pt3.x, pt3.y);
g.closePath();
if (this.isWireFrame) {
g.stroke();
}
// 面の塗りつぶし
if (this.isFill) {
// 描画色の指定
if (this.isColorful && this.hasColor) {
// フルカラー
var col = this.setHex(parseInt(face.color, 16));
var hsv = this.RGBtoHSV(col.r, col.g, col.b, false);
var rgb = this.HSVtoRGB(hsv.h, hsv.s, hsv.v - Math.round((1 - face.nz) * 50));
}
else {
// 単色
var rgb = this.HSVtoRGB(0.4 * 360, 0.5 * 255, face.nz * 255);
}
g.fillStyle = 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'; // 面の塗りつぶし
g.fill();
}
if (this.isTexture) {
// テクスチャの描画
var vertex_list = [pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y];
var uv_list = [face.coords[0].u, face.coords[0].v, face.coords[1].u, face.coords[1].v, face.coords[2].u, face.coords[2].v];
this.drawTriangle(g, this.texture, vertex_list, uv_list, i);
g.save();
g.globalAlpha = 1 - face.nz;
g.fillStyle = 'Black';
g.fill();
g.restore();
}
}
}
// 三角形テクスチャ描画
drawTriangle(g, img, vertex_list, uv_list, i) {
var _Ax = vertex_list[2] - vertex_list[0];
var _Ay = vertex_list[3] - vertex_list[1];
var _Bx = vertex_list[4] - vertex_list[0];
var _By = vertex_list[5] - vertex_list[1];
var Ax = (uv_list[2] - uv_list[0]) * img.width;
var Ay = (uv_list[3] - uv_list[1]) * img.height;
var Bx = (uv_list[4] - uv_list[0]) * img.width;
var By = (uv_list[5] - uv_list[1]) * img.height;
//逆行列を求める
var m = new M22();
m._11 = Ax;
m._12 = Ay;
m._21 = Bx;
m._22 = By;
var mi = m.getInvert();
if (!mi) return;
//マトリックス変換値を求める
var a, b, c, d;
a = mi._11 * _Ax + mi._12 * _Bx;
c = mi._21 * _Ax + mi._22 * _Bx;
b = mi._11 * _Ay + mi._12 * _By;
d = mi._21 * _Ay + mi._22 * _By;
g.save();
g.beginPath();
g.moveTo(vertex_list[0], vertex_list[1]);
g.lineTo(vertex_list[2], vertex_list[3]);
g.lineTo(vertex_list[4], vertex_list[5]);
g.clip(); //三角形に切り取る
if (this.mousePoint != undefined && g.isPointInPath(this.mousePoint.x, this.mousePoint.y)) {
g.fillStyle = 'rgb(255,0,0)'; // 面の塗りつぶし
g.fill();
g.restore();
this.faceNo = i;
this.mousePoint != undefined;
return;
}
var e = vertex_list[0] - (a * uv_list[0] * img.width + c * uv_list[1] * img.height);
var f = vertex_list[1] - (b * uv_list[0] * img.width + d * uv_list[1] * img.height);
g.transform(a, b, c, d, e, f);
g.drawImage(img, 0, 0);
g.restore();
}
setHex(hex:number) {
hex = Math.floor(hex);
var r: number = (hex >> 16 & 255);
var g: number = (hex >> 8 & 255);
var b: number = (hex & 255);
return { 'r': r, 'g': g, 'b': b };
}
getColorHex(r:number, g:number, b:number):number {
return (r * 255) << 16 ^ (g * 255) << 8 ^ (b * 255) << 0;
}
getColorHexString(r: number, g: number, b: number):string {
return ('000000' + this.getColorHex(r, g, b).toString(16)).slice(- 6);
}
RGBtoHSV(r: number, g: number, b: number, coneModel:boolean) {
var h: number, // 0..360
s: number, v: number, // 0..255
max = Math.max(Math.max(r, g), b),
min = Math.min(Math.min(r, g), b);
// hue の計算
if (max == min) {
h = 0; // 本来は定義されないが、仮に0を代入
} else if (max == r) {
h = 60 * (g - b) / (max - min) + 0;
} else if (max == g) {
h = (60 * (b - r) / (max - min)) + 120;
} else {
h = (60 * (r - g) / (max - min)) + 240;
}
while (h < 0) {
h += 360;
}
// saturation の計算
if (coneModel) {
// 円錐モデルの場合
s = max - min;
} else {
s = (max == 0)
? 0 // 本来は定義されないが、仮に0を代入
: (max - min) / max * 255;
}
// value の計算
v = max;
return { 'h': h, 's': s, 'v': v };
}
// HSBからRGB変換
HSVtoRGB(h: number, s: number, v: number) {
var r: number, g: number, b: number; // 0..255
while (h < 0) {
h += 360;
}
h = h % 360;
// 特別な場合 saturation = 0
if (s == 0) {
// → RGB は V に等しい
v = Math.round(v);
return { 'r': v, 'g': v, 'b': v };
}
s = s / 255;
var i: number = Math.floor(h / 60) % 6,
f = (h / 60) - i,
p = v * (1 - s),
q = v * (1 - f * s),
t = v * (1 - (1 - f) * s);
switch (i) {
case 0:
r = v; g = t; b = p; break;
case 1:
r = q; g = v; b = p; break;
case 2:
r = p; g = v; b = t; break;
case 3:
r = p; g = q; b = v; break;
case 4:
r = t; g = p; b = v; break;
case 5:
r = v; g = p; b = q; break;
}
return { 'r': Math.round(r), 'g': Math.round(g), 'b': Math.round(b) };
}
}
// メインクラス
class Study3DApp {
private mousePosition: Point; // マウス位置の初期化
private phi = 0.30; // x軸周りの回転角
private theta = 0.50; // y軸周りの回転角
private isDrag: boolean = false;
private elmWireFrame: HTMLInputElement;
private elmFill: HTMLInputElement;
private elmTexture: HTMLInputElement;
private elmColorful: HTMLInputElement;
private elmCulling: HTMLInputElement;
private elmAxis: HTMLInputElement;
private elmAxisCube: HTMLInputElement;
private elmSpeed: HTMLInputElement;
private image: HTMLImageElement;
private width: number;
private height: number;
private center: Point;
private scale: number;
private context: CanvasRenderingContext2D;
private context2: CanvasRenderingContext2D;
private modelObj: ModelObject;
private axisCube: AxisCube;
private axis = [];
private index = 0;
private json;
private frameCount = 0;
constructor(canvas: HTMLCanvasElement, canvas2: HTMLCanvasElement) {
this.context = canvas.getContext("2d");
this.context2 = canvas2.getContext("2d");
this.width = canvas.width = canvas2.width = 448; //window.innerWidth;
this.height = canvas.height = canvas2.height = 448; //window.innerHeight;
this.mousePosition = new Point(this.width / 2, this.height / 2);
this.elmWireFrame = <HTMLInputElement>document.getElementById('wireFrameCheck');
this.elmFill = <HTMLInputElement>document.getElementById('fillCheck');
this.elmTexture = <HTMLInputElement>document.getElementById('textureCheck');
this.elmColorful = <HTMLInputElement>document.getElementById('colorfulCheck');
this.elmCulling = <HTMLInputElement>document.getElementById('cullingCheck');
this.elmAxis = <HTMLInputElement>document.getElementById('axisCheck');
this.elmAxisCube = <HTMLInputElement>document.getElementById('axisCubeCheck');
this.elmSpeed = <HTMLInputElement>document.getElementById('speed');
// 中央位置の設定
this.center = new Point(this.width / 2, this.height / 2);
// 描画スケールの設定
this.scale = this.width * 0.6 / 2
this.modelObj = new ModelObject(this.center, this.scale, 'black');
canvas.addEventListener("mousemove", (e: MouseEvent) => {
if (!this.isDrag) return;
// 回転角の更新
this.theta += (e.clientX - this.mousePosition.x) * 0.01;
this.phi += (e.clientY - this.mousePosition.y) * 0.01;
// x軸周りの回転角に上限を設定
this.phi = Math.min(this.phi, Math.PI / 2);
this.phi = Math.max(this.phi, -Math.PI / 2);
// マウス位置の更新
this.mousePosition = new Point(e.clientX, e.clientY);
// 描画
this.render();
});
canvas.addEventListener("mousedown", (e: MouseEvent) => {
if (e.button == 0) {
// マウスボタン押下イベント
this.isDrag = true;
}
// マウス位置の更新
this.mousePosition = new Point(e.offsetX | 0, e.offsetY | 0);
this.modelObj.mousePoint = this.mousePosition;
});
canvas.addEventListener("mouseup", (e: MouseEvent) => {
// マウスボタン離されたイベント
this.isDrag = false;
});
// 各チェックボックス変更
this.elmWireFrame.addEventListener("change", () => {
this.modelObj.isWireFrame = this.elmWireFrame.checked;
this.render();
});
this.elmFill.addEventListener("change", () => {
this.modelObj.isFill = this.elmFill.checked;
this.render();
});
this.elmTexture.addEventListener("change", () => {
this.modelObj.isTexture = this.elmTexture.checked;
this.render();
});
this.elmColorful.addEventListener("change", () => {
this.modelObj.isColorful = this.elmColorful.checked;
this.render();
});
this.elmCulling.addEventListener("change", () => {
this.modelObj.isCulling = this.elmCulling.checked;
this.render();
});
this.elmAxis.addEventListener("change", () => {
this.render();
});
this.elmAxisCube.addEventListener("change", () => {
this.render();
});
this.elmSpeed.addEventListener("change", () => {
document.getElementById("speedDisp").innerHTML = this.elmSpeed.value;
});
this.axisCube = new AxisCube(this.center, this.scale, 'darkgray');
// 軸 立方体より少しはみ出すために1.2倍長くする
this.axis.push(new Axis(this.center, this.scale * 1.2, 'blue', 'x'));
this.axis.push(new Axis(this.center, this.scale * 1.2, 'green', 'y'));
this.axis.push(new Axis(this.center, this.scale * 1.2, 'red', 'z'));
// モデルデータ読込
this.changeModelData();
}
// モデルデータロード完了
changeModelData() {
// モデルデータ読込
this.index = 0;
this.frameCount = 0;
//this.JSONLoader("monster.js", (() => this.onJSONLoaded()));
//this.JSONLoader("robot.js", (() => this.onJSONLoaded()));
this.JSONLoader("dice41.js", (() => this.onJSONLoaded()));
}
// モデルデータロード完了
onJSONLoaded() {
// モデルデータの生成
this.modelObj.createModel(this.json);
// モデルデータの設定
this.modelObj.setModelData(this.index);
this.image = new Image();
this.image.src = "test.png";
//this.image.src = "サイコロ2561.png";
//this.image.src = "monster.jpg";
//this.image.src = "robot.jpg";
this.image.onload = (() => this.imageReady(this));
}
// イメージ読込完了
imageReady(that) {
var ctx = this.context2;
var w = this.image.width;
var h = this.image.height;
// 初期描画および画像データ退避
ctx.drawImage(this.image, 0, 0, w, h);
this.modelObj.texture = this.image;
// 描画
this.render();
// タイマー
setInterval((() => this.onFrame()), 1000 / 30);
}
// 情報表示
drawInfo() {
var elm = document.getElementById("info");
elm.innerText = 'theta: ' + this.theta.toFixed(2) + ' / phi: ' + this.phi.toFixed(2) + ' / FaceNo: ' + this.modelObj.faceNo + ' / x,y: ' + this.mousePosition.x + ',' + this.mousePosition.y;
}
// 描画クリア
drawClear(g: CanvasRenderingContext2D) {
g.beginPath();
g.fillStyle = 'aliceblue';
g.fillRect(0, 0, this.width, this.height);
}
// 描画
render() {
var g: CanvasRenderingContext2D = this.context;
// 描画クリア
this.drawClear(g);
// モデル描画
this.modelObj.setScreenPosition(this.theta, this.phi);
this.modelObj.draw(g);
// 軸立方体描画
if (this.elmAxisCube.checked) {
this.axisCube.setScreenPosition(this.theta, this.phi);
this.axisCube.draw(g);
}
if (this.elmAxis.checked) {
// 軸描画
for (var i = 0; i < this.axis.length; i++) {
this.axis[i].setScreenPosition(this.theta, this.phi);
this.axis[i].draw(g);
}
}
// 情報表示
this.drawInfo();
}
// 毎回フレーム
onFrame() {
if ((this.frameCount % (Number(this.elmSpeed.value) * 3)) == 0) {
this.frameCount = 0;
// モデルデータの設定
this.modelObj.setModelData(this.index);
this.index++;
this.index %= this.modelObj.animeLength;
}
this.render();
this.frameCount++;
}
// モデルJSONデータ読み込み
JSONLoader(file, callback: Function) {
var x = new XMLHttpRequest();
x.open('GET', file);
x.onreadystatechange = () => {
if (x.readyState == 4) {
this.json = JSON.parse(x.responseText);
// 読込完了コールバック
callback();
}
}
x.send();
}
}
window.onload = () => {
var app = new Study3DApp(<HTMLCanvasElement>document.getElementById('content'),
<HTMLCanvasElement>document.getElementById('content2'));
};