前回の「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')); };