class App { constructor( canvas ) { this.cc = canvas.getContext( "2d" ); //データ「立方体」 //3DCGのデータは基本的には、 if( typeof modelGroups === "undefined" ) { window.modelGroups = new Array(); } this.modelGroups = modelGroups; this.modelGroup = { "cube" : { name : "cube", //↓頂点x,y,zがたくさんあって、 tens : [ [ -1, 1, -1 ], //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: 奥 左下 ], //↓どの頂点をつないで面にするかというデータがあるだけです。 mens : [ [ 0, 1, 2, 3 ], //正面 [ 5, 4, 7, 6 ], //背面 [ 1, 0, 4, 5 ], //天面 [ 2, 6, 7, 3 ], //底面 [ 0, 3, 7, 4 ], //側面左 [ 5, 6, 2, 1 ], //側面右 ], rotateX : 0, rotateY : 0, rotateZ : 0, scaleX : 100, //tensの値をどれくらい拡大するか scaleY : 100, scaleZ : 100, baseColor : { r : 128, g : 255, b : 128 }, //拡散反射色(面の色) } } // modelGroup this.modelGroups.push( this.modelGroup ); //--- messages this.messages = [ "dragon", "sphere", "torus + sphere", "20 nurbs", "cube", ] this.message = this.getMessage( this.messages.length - 1 ); // 土星型モデルの大きさ調整 // let idx = 3; // for( let name in modelGroups[ idx ] ) { // modelGroups[ idx ][ name ].scaleX *= 5; // modelGroups[ idx ][ name ].scaleY *= 5; // modelGroups[ idx ][ name ].scaleZ *= 5; // } //---カメラ設定 this.posX = 0; this.posY = 0; this.posZ = 800; //---回転アニメーション this.rotationX = 0.4; this.rotationY = 0; this.rotationZ = 0; if( 0 ) { // クリックされたら、モデルを切り替える this.cc.canvas.onclick = function( e ) { let index = this.modelGroups.indexOf( this.modelGroup ); index = ( index + 1 ) % this.modelGroups.length; this.modelGroup = this.modelGroups[ index ]; }.bind( this ); } //アニメ関係 this.timerMs = 50; this.timerId = null; this.frameTmBak = 0; //debug. for( let i = 0; i < this.modelGroups.length; i++ ) { console.log( "model", i ); let modelGroup = this.modelGroups[ i ]; for( let name in modelGroup ) { console.log( "\t", name ); } } } // constructor() start() { //check. すでに起動中 if( this.timerId != null ) return; //frame() が呼ばれ、frame() から draw() が呼ばれる。 this.frame( 0 ); } stop() { //check. すでに停止中 if( this.timerId == null ) return; cancelAnimationFrame( this.timerId ); this.timerId = null; } frame( tm ) { if( tm - this.frameTmBak >= this.timerMs ) { this.frameTmBak = tm; this.rotationY += 0.04; //check. 1回転でモデルを切り替える if( this.rotationY > Math.PI * 2 ) { this.rotationY = 0; let index = this.modelGroups.indexOf( this.modelGroup ); index = ( index + 1 ) % this.modelGroups.length; this.modelGroup = this.modelGroups[ index ]; this.message = this.getMessage( index ); } this.draw( this.cc ); } this.timerId = requestAnimationFrame( this.frame.bind( this ) ); } draw( cc ) { /* draw() の内容 for1 各モデルごとに処理 ↓ ↓ for1-1 頂点の3D座標を計算、2D座標を計算 ↓ ↓ ↓ for1-2 allmensを作成 ↓ for2 2D画面へ描画 */ // drawでは、3D計算が行われ、2D画面へ描画する。 // 3D計算が行われ、 //---カメラ設定 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++ ) { // 以下3D座標について計算 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 = this.kaiten( x, y, this.rotationZ + model.rotateZ ); x = r.X; y = r.Y; //Y軸回転(地上をぐるっとまわるような回転) r = this.kaiten( x, z, this.rotationY + model.rotateY ); x = r.X; z = r.Y; //X軸回転(首を上下に振るような回転) r = this.kaiten( z, y, this.rotationX + model.rotateX ); z = r.X; y = r.Y; //位置へ移動 x += this.posX; y += this.posY; z += this.posZ; // 以上3D座標について計算、以下2D座標について計算 //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 ]; //allmensへ追加する let theMen = new Object(); theMen.idx = j; theMen.men = men; theMen.model = model; allmens.push( theMen ); }//for mens }//for modelGroup // 以上で、以下の描画で使う、allmens が準備できた。 // 2D画面へ描画する。 cc.clearRect( 0, 0, cc.canvas.width, cc.canvas.height ); cc.save(); //現在の画面状態を保存しておく //画面の中央を原点とする(画面状態の変更1) cc.translate( cc.canvas.width / 2, cc.canvas.height / 2 ); //画面を上下反転する(画面状態の変更2) cc.scale( 1, -1 ); // for2 2D画面へ描画 for( let i = 0; i < allmens.length; i++ ) { let theMen = allmens[ i ]; let men = theMen.men; 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(); cc.strokeStyle = "black"; cc.stroke(); }//for allmens cc.restore(); //保存しておいた画面状態を元に戻す } // draw() kaiten( x, y, theta2 ) { // 数学:「ある座標を sin, cos を使って回転する」 // 座標 x, y を、角度 theta2 だけ回転させた座標 { X, Y } を返す。 let theta1 = Math.atan2( y, x ); let hankei = Math.sqrt( x * x + y * y ); return { X : Math.cos( theta1 + theta2 ) * hankei, Y : Math.sin( theta1 + theta2 ) * hankei } } getMessage( index ) { let numofpol = 0; for( let name in this.modelGroup ) { numofpol += this.modelGroup[ name ].mens.length; } return this.messages[ index ] + " ( " + numofpol + " polygons )"; } } // App() addEventListener( "load", function() { let whitearea = document.getElementsByClassName( "whitearea" )[ 0 ]; let canvas = document.getElementById( "topCanvas" ); // Object.assign( canvas, { // width : 512 * 2, // height : 448 * 2.5, // } ); // Object.assign( canvas.style, { // border : "solid 1px blue", // boxSizing : "border-box", // position : "absolute", // left : 0, // top : "-100px", // width : "100%", // zIndex : -1, // } ); Object.assign( whitearea.style, { position : "relative", zIndex : 0, } ); whitearea.appendChild( canvas ); //check. if( typeof apps === "undefined" ) { apps = new Array(); } let app = new App( canvas ); apps.push( app ); } );