/// 7 prog_3dcg.html用 ServicePack1 /// /* prog_3dcg.html の draw() 関数を書き換えます。 面を塗ります。 その際、手前のポリゴンによって後ろのポリゴンが隠れる陰面消去を行います。 また、ポリゴンの向きが向こう側になっているポリゴンを描かないタイプの陰面消去も行います。 光源に対する面の角度によって、面の色の明度を調整する処理(陰影)を加えます。 */ App.prototype.kougen = new Object(); App.prototype.kougen.pos = { x : -300, y : 400, z : 200 } App.prototype.draw = function( cc ) { /* draw() の内容 for1 各モデルごとに処理 ↓ ↓ for1-1 頂点の3D座標を計算、2D座標を計算 ↓ ↓ ↓ for1-2 allmensを作成 ↓ 面をソート ↓ for2 2D画面へ描画 */ cc.clearRect( 0, 0, cc.canvas.width, cc.canvas.height ); // model name & polygons let fontSize = 12; cc.font = fontSize + "px ''"; cc.fillStyle = "gray"; let numofbers = 9 - Math.floor( ( this.rotationY * ( 180 / Math.PI ) ) / 360 * 10 ); let text = Array( numofbers + 1 ).join( "●" ) + " " + this.message; //let text = ( Math.floor( this.rotationY * ( 180 / Math.PI ) ) ) + "° : " + this.message; let textWCAN = cc.measureText( text ).width; let textHCAN = fontSize; let cs = getComputedStyle( cc.canvas ); let canvasLPAG = Number( cs.left.replace( /px/, "" ) ); let canvasTPAG = Number( cs.top.replace( /px/, "" ) ); let canvasWPAG = Number( cs.width.replace( /px/, "" ) ); let rate = cc.canvas.width / canvasWPAG; let textPosLCAN = cc.canvas.width - textWCAN - canvasLPAG * rate; let textPosTCAN = textHCAN - canvasTPAG * rate; cc.fillText( text, textPosLCAN, textPosTCAN ); cc.save(); //画面の中央を原点とする cc.translate( cc.canvas.width / 2, cc.canvas.height / 2 ); //画面を上下反転する cc.scale( 1, -1 ); // drawでは、3D計算が行われ、2D画面へ描画する。 //---カメラ設定 let s = 50; //焦点距離 let zoom = 17.81; //(この拡大率で shade3D の画面と一致) let allmens = new Array(); //すべての面を集めた配列 // for1 各モデルごとに処理 for( let modelName in this.modelGroup ) { let model = this.modelGroup[ modelName ]; //全頂点の位置を計算 let tensC = new Array(); //Cはcalc "計算した" のC。 let tens = model.tens; // for1-1 頂点の3D座標を計算、2D座標を計算 for( let i = 0; i < tens.length; i++ ) { let x = tens[ i ][ 0 ]; let y = tens[ i ][ 1 ]; let z = tens[ i ][ 2 ]; let r; // kaitenByCxCy() の返値受け取り用 //scale倍 x *= model.scaleX; y *= model.scaleY; z *= model.scaleZ; //Y軸回転(地上をぐるっとまわるような回転) r = this.kaiten( x, z, this.rotationY ); x = r.X; z = r.Y; //X軸回転(首を上下に振るような回転) r = this.kaiten( z, y, this.rotationX ); z = r.X; y = r.Y; //位置へ移動 x += this.posX; y += this.posY; z += this.posZ; //3Dの座標を2Dの座標に変換する let h = x * ( s / z ); let v = y * ( s / z ); //画面を引き延ばし h *= zoom; v *= zoom; //値を保管 let tenC = new Object(); tenC.x = x; tenC.y = y; tenC.z = z; tenC.h = h; tenC.v = v; tensC.push( tenC ); }//for tens model.tensC = tensC; //この時点で全頂点の位置を計算済み // すべての面を集めた配列 allmens を作成する // for1-2 allmensを作成 let mens = model.mens; for( let j = 0; j < mens.length; j++ ) { let men = mens[ j ]; let jusin; //陰面消去1と陰面消去2で使用する値 let housen; //陰面消去2と陰影処理で使用する値 //check. 面が視点のほうを向いていないなら、その面は除去 let tmpTens = new Array(); for( let i = 0; i < men.length; i++ ) tmpTens.push( model.tensC[ men[ i ] ] ); jusin = this.getJusin( tmpTens ); housen = this.getHousen( tmpTens ); //check. 面が視点のほうを向いていないなら、その面は除去 //(陰面消去2 「法線ベクトル法」) if( ! this.checkVisibility( housen, jusin ) ) continue; //面を構成する点を順にたどる let sumZ = 0; for( let k = 0; k < men.length; k++ ) { let tenIDX = men[ k ]; let ten = model.tensC[ tenIDX ]; sumZ += ten.z; } //allmensへ追加する let theMen = new Object(); theMen.idx = j; theMen.men = men; theMen.avgZ = sumZ / men.length; theMen.jusin = jusin; theMen.housen = housen; theMen.model = model; allmens.push( theMen ); }//for mens }//for modelGroup // 面をソート // すべての面を奥から手前の順にソート // (陰面消去1 「画家のアルゴリズム」) allmens.sort( function( a, b ) { if( a.avgZ > b.avgZ ) return -1; else if( a.avgZ < b.avgZ ) return 1; else return 0; } ); // for2 2D画面へ描画 for( let i = 0; i < allmens.length; i++ ) { let theMen = allmens[ i ]; let men = theMen.men; let housen = theMen.housen; let jusin = theMen.jusin; let model = theMen.model; //面を構成する点を順にたどる cc.beginPath(); for( let j = 0; j < men.length; j++ ) { let tenIDX = men[ j ]; let ten = model.tensC[ tenIDX ]; let h = ten.h; let v = ten.v; //最初の点はmoveTo、続く点はlineTo if( j == 0 ) cc.moveTo( h, v ); else cc.lineTo( h, v ); }//for men cc.closePath(); //面の中を塗る //陰影処理(ランバート反射)を加える let kougenP = { x : this.kougen.pos.x - jusin.x, y : this.kougen.pos.y - jusin.y, z : this.kougen.pos.z - jusin.z, }; //kougen.posは原点から見た位置ですが、 //陰影処理のために、これを面から見た位置に直し、それをkougenPとします。 let RGB = this.lambelt( this.toNorm( kougenP ), housen, model.baseColor ); //塗り+線 cc.fillStyle = this.rgb2str( RGB ); cc.fill(); cc.strokeStyle = "black"; cc.stroke(); }//for allmens cc.restore(); //画面情報を元に戻す }//draw //---UTL App.prototype.getJusin = function( p ) { //数学: 重心 //面を構成する各頂点の、x,y,z各成分の平均値を得る let res = { x : 0, y : 0, z : 0 } for( let i = 0; i < p.length; i++ ) { res.x += p[ i ].x; res.y += p[ i ].y; res.z += p[ i ].z; } res.x /= p.length; res.y /= p.length; res.z /= p.length; return res; } App.prototype.getHousen = function( p ) { //数学: 法線を得る let x1 = p[ 0 ].x, y1 = p[ 0 ].y, z1 = p[ 0 ].z; let x2 = p[ 1 ].x, y2 = p[ 1 ].y, z2 = p[ 1 ].z; let x3 = p[ 2 ].x, y3 = p[ 2 ].y, z3 = p[ 2 ].z; //法線 // http://www.myu.ac.jp/~makanae/CG/cg1_14.htm let res = new Object(); res.x = (y2-y1)*(z3-z2)-(z2-z1)*(y3-y2); res.y = (z2-z1)*(x3-x2)-(x2-x1)*(z3-z2); res.z = (x2-x1)*(y3-y2)-(y2-y1)*(x3-x2); res = this.toNorm( res ); return res; } App.prototype.checkVisibility = function( housen, jusin ) { //視点の方向(eye) let sisen = this.objCpy( jusin ); sisen.x *= -1; sisen.y *= -1; sisen.z *= -1; //法線と視点方向の内積(※cosθ) let a = this.naiseki( housen, sisen ); return a > 0; } App.prototype.lambelt = function( housen, kougen, RGB ) { let cosTheta = this.naiseki( housen, kougen ); let directPer = 0.5; //直接光の割合 let ambientPer = 1 - directPer; //環境光の割合 //色成分をさらに直接光と環境光の割合で分割、直接光についてはcosTheta(0~1)でさらにしぼる let dirR = RGB.r * directPer * cosTheta; let ambR = RGB.r * ambientPer; let dirG = RGB.g * directPer * cosTheta; let ambG = RGB.g * ambientPer; let dirB = RGB.b * directPer * cosTheta; let ambB = RGB.b * ambientPer; //分割したものを再度合体(直接光分にcosThetaを掛けたかっただけ) let res = new Object(); res.r = Math.round( dirR + ambR ); res.g = Math.round( dirG + ambG ); res.b = Math.round( dirB + ambB ); return res; } //---Tool App.prototype.rgb2str = function( RGB ) { //便利目的 return "RGB(" + RGB.r + "," + RGB.g + "," + RGB.b + ")"; } App.prototype.objCpy = function( obj ) { //便利目的 let res = new Object(); for( let name in obj ) res[ name ] = obj[ name ]; return res; } App.prototype.toNorm = function( p ) { //数学: 単位ベクトル化 //(原点から座標pまでの距離を1にした座標を返す) let len = Math.sqrt( p.x * p.x + p.y * p.y + p.z * p.z ); let res = new Object(); res.x = p.x / len; res.y = p.y / len; res.z = p.z / len; return res; } App.prototype.naiseki = function( v1, v2 ) { //数学: 内積を得る let a = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; return a; }