/// 7 prog_3dcg.html用 ServicePack1 ///
/*
7 prog_3dcg.html の draw() 関数を書き換えます。
面を塗ります。
その際、後ろのポリゴンの上から手前のポリゴンを描くタイプの陰面消去を行います。
また、ポリゴンの向きが向こう側になっているポリゴンを描かないタイプの陰面消去も行います。
そして、光源に対する面の角度によって、面の色の明度を調整する処理(陰影)を加えます。
*/
kougen = new Object();
kougen.pos = { x : -300, y : 400, z : 200 }
//この kougen は簡易的なものです。
//「最後に読まれた FBX 形式モデルの光源の値」が有効になります。
//FBX を1つも読まなかった場合は上記の値になります。
function draw( cc ) {
/*
draw() の内容
for1 各モデルごとに処理
↓
↓ for1-1 頂点の3D座標を計算、2D座標を計算
↓ ↓
↓ for1-2 allmensを作成
↓
面をソート
↓
for2 2D画面へ描画
*/
cc.clearRect( 0, 0, cc.canvas.width, cc.canvas.height );
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 modelGroup ) {
let model = 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;
//Z軸回転(正面で指先で円を描くような回転)
r = kaiten( x, y, rotationZ );
x = r.X;
y = r.Y;
//Y軸回転(地上をぐるっとまわるような回転)
r = kaiten( x, z, rotationY );
x = r.X;
z = r.Y;
//X軸回転(首を上下に振るような回転)
r = kaiten( z, y, rotationX );
z = r.X;
y = r.Y;
//位置へ移動
x += posX;
y += posY;
z += 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 = getJusin( tmpTens );
housen = getHousen( tmpTens );
//check. 面が視点のほうを向いていないなら、その面は除去
//(陰面消去2 「法線ベクトル法」)
if( ! 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 : kougen.pos.x - jusin.x,
y : kougen.pos.y - jusin.y,
z : kougen.pos.z - jusin.z,
};
//kougen.posは原点から見た位置ですが、
//陰影処理のために、これを面から見た位置に直し、それをkougenPとします。
let RGB = lambelt( toNorm( kougenP ), housen, model.baseColor );
//塗り+線
cc.fillStyle = rgb2str( RGB );
cc.fill();
cc.strokeStyle = "black";
cc.stroke();
}//for allmens
cc.restore(); //画面情報を元に戻す
}//draw
//---UTL
function getJusin( 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;
}
function getHousen( 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 = toNorm( res );
return res;
}
function checkVisibility( housen, jusin ) {
//視点の方向(eye)
let sisen = objCpy( jusin );
sisen.x *= -1;
sisen.y *= -1;
sisen.z *= -1;
//法線と視点方向の内積(※cosθ)
let a = naiseki( housen, sisen );
return a > 0;
}
function lambelt( housen, kougen, RGB ) {
let cosTheta = 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
function rgb2str( RGB ) {
//便利目的
return "RGB(" + RGB.r + "," + RGB.g + "," + RGB.b + ")";
}
function objCpy( obj ) {
//便利目的
let res = new Object();
for( let name in obj ) res[ name ] = obj[ name ];
return res;
}
function toNorm( 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;
}
function naiseki( v1, v2 ) {
//数学: 内積を得る
let a = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
return a;
}