/* 【説明】 このファイルを html ファイルの script タグの src 属性で指定するだけで、動作開始するように作ったつもり。 目的のソフトウェアを App というクラスにまとめてある。 コメントで、「以下 変更しない部分」、「以下 変更する部分」のようにファイルを大きく2分してある。 このファイルを再利用するためです。 html の読み込みが完了した時点で、App クラスの onloadx1() 関数が呼ばれる。 onloadx1() と onloadx2() は処理的に分ける必要はないが、変更する必要のない部分と、 目的のソフトウェアを実装する部分とに分別するために分けた。 リサイズされたら、目的の部分(canvas 要素)はサイズを自動で合わせるようになっている。 【目的のソフトウェア 部分】 ページのロードが完了すると、このプログラムは画面に大きく、多数の四角形の 3DCG を描画する。 右上の STOP ボタンで停止と再開を行う。 アニメを行う基本的なプログラムになっている。 onloadx2() で 3DCG の各モデルの定義を行い、その後すぐに draw() している。 つづく start() でアニメが開始される。( setInterval() による run() の定期実行を行う) run() は各アニメの数値の推移を行い、 "すい星"(大きな四角形の後に小さな四角形がパラパラと続く様子) の尾の作成、消去など行い、最後に draw() している。 draw() は 3DCG の描画の基本的なプログラムが書かれている。 1. 画面に描く各四角形の4頂点の座標計算 2. 奥から手前の順になるようにソート(画家のアルゴリズム) 3. 描画 webGL やその他 3DCG などのライブラリは一切使用していません。 */ //---以下 変更しない部分 var idxjs = new App(); //ページ読み込み完了で開始 addEventListener( "load", function() { var parentEL = document.getElementById( "whiteareaID" ); idxjs.onloadx1( parentEL ); //onloadx1は最後にonloadx2を呼んでいる }, false ); function App() { //Appクラスのコンストラクタ } App.prototype.onloadx1 = function( parentEL ) { //(※正直言うとcanvasのサイズについて多少混乱中…) //check. if( ! parentEL ) parentEL = document.body; //canvasを置く親要素 this.parentEL = parentEL; with( this.parentEL.style ) { position = "relative"; zIndex = 0; } //アニメの停止ボタン 親要素の上部に配置 this.swEL = document.createElement( "div" ); this.parentEL.appendChild( this.swEL ); with( this.swEL.style ) { border = "solid 0px black"; position = "absolute"; right = "0px"; top = "0px"; boxSizing = "border-box"; zIndex = 2; padding = ".5em 1em"; } this.swEL.innerHTML = "STOP"; //CANVAS要素 親要素の上部に配置 this.canvasEL = document.createElement( "canvas" ); this.parentEL.appendChild( this.canvasEL ); this.canvasEL.setAttribute( "id", "idxjs_canvasELID" ); with( this.canvasEL.style ) { border = "solid 0px black"; position = "absolute"; left = "0px"; top = "0px"; boxSizing = "border-box"; zIndex = -1; } //true にするとドットがシャープになる。falseはアンチエイリアスが入る(通常)。IEは非対応 if( true ) { this.canvasEL.style.imageRendering = "pixelated"; this.canvasEL.style.imageRendering = "optimizeSpeed"; } this.canvas = this.canvasEL.getContext( "2d" ); this.onresizex(); //resize設定 this_closure = this; addEventListener( "resize", function( e ) { this_closure.onresizex( e ); }, false ); this.onloadx2(); }//onloadx1 App.prototype.onresizex = function( e ) { //リサイズされた親要素に合わせて、サイズ変更 var waRect = this.parentEL.getBoundingClientRect(); this.canvasW = waRect.width; this.canvasH = 480; this.canvasEL.setAttribute( "width", this.canvasW ); this.canvasEL.setAttribute( "height", this.canvasH ); with( this.canvasEL.style ) { width = this.canvasW; height = this.canvasH; } //true にすると解像度を下げる。false は通常。 if( true ) { var pixelsize = 2; //ドットの大きさ2~ this.canvasEL.style.width = this.canvasEL.width + "px"; //実物画面大きさとして this.canvasEL.style.height = this.canvasEL.height + "px"; this.canvasEL.width /= pixelsize; //解像度として this.canvasEL.height /= pixelsize; this.canvas.scale( 1 / pixelsize, 1 / pixelsize ); } }//onresizex //---以上 変更しない部分 //---以下 変更する部分 目的のソフトウェア App.prototype.onloadx2 = function() { /* 使える変数: this.canvasEL this.canvasW this.canvasH */ //3DCGセッティング //四角形 モデル リンク形状が参照する this.squareMaster = { tens : [ [ -1, -1, 0 ], [ 1, -1, 0 ], [ 1, 1, 0 ], [ -1, 1, 0 ] ], }; //水色のすい星 リンク形状 this.square0 = { ref : this.squareMaster, scale : 100, pos : xyz( 600, 0, 1000 ), //xが公転アニメの半径となる rotation : xyz( 0, 0, 0 ), type : 0, //0: normal, 1:tail } //四角形 リンク形状 this.squares = [ this.square0, { //一枚のピンク色の四角形 ref : this.squareMaster, scale : 75, pos : xyz( 500, 0, 1000 ), //xが公転アニメの半径となる rotation : xyz( 0, 0, 0 ), type : 0, //0: normal, 1:tail } //以降、尾がrun()ごとに追加されていく ] //アニメ 他の3DCG形状と似せたオブジェクトにして扱う this.animates = new Array(); this.animates.push( { //公転 pos : xyz( 0, 0, 1000 ), rotation : xyz( 0, 0, 0 ), rotationAdd : xyz( 0, 0.05, 0 ), } ); this.animates.push( { //面の自転 pos : xyz( 0, 0, 0 ), rotation : xyz( 0, 0, 0 ), rotationAdd : xyz( 0, 0, -0.05 ), } ); this.animates.push( { //公転 逆 ピンクの四角形用 pos : xyz( -100, 0, 1000 ), rotation : xyz( 0, 0, 3.14/1.75 ), rotationAdd : xyz( 0, 0.05, 0 ), } ); //カメラ 他の3DCG形状と似せたオブジェクトにして扱う this.camera = { pos : xyz( 0, 150, 00 ), s : 40, zoom : 17.25, rotation : xyz( -0.3, 0, -0.2 ), }; mouseX = 0; mouseY = 0; this.draw(); this.start(); onmousemove = function( e ) { //this is canvasEL. mouseX = e.clientX; mouseY = e.clientY; }; } App.prototype.start = function() { //タイマ var this_closure = this; timerID = setInterval( function() { this_closure.run(); }, 80 ); } App.prototype.run = function() { //各回転アニメを実行する for( var i = 0; i < this.animates.length; i++ ) { with( this.animates[ i ] ) { rotation.x += rotationAdd.x; rotation.y += rotationAdd.y; rotation.z += rotationAdd.z; //check. 変数の上限に到達しないように変数値をリセットする // rotation.x %= 6.28; // rotation.y %= 6.28; // rotation.z %= 6.28; //ただ、計算すると到達するのは 4.6237 * ( 10の300乗 ) 年後なので //コメントアウト…計算は正しいはずだけどホントかな。。 } } //すい星の尾の誕生 run()ごとに1個誕生 var square = objcopy( this.square0 ); square.type = 1; //0:すい星本体 1:すい星の尾 という意味 square.cntmax = 15; //尾の残存カウント run()15回で尾は消滅 square.cnt = 1; var rate = 70; //尾の位置ばらつき square.pos.x += Math.random() * rate; square.pos.y += Math.random() * rate; square.pos.z += Math.random() * rate * 0.5; var rate = 1; //尾の回転ばらつき square.rotation.x += Math.random() * rate; square.rotation.y += Math.random() * rate; square.rotation.z += Math.random() * rate; this.squares.push( square ); //すい星の尾の消滅チェック for( var i = this.squares.length - 1; i >= 0; i-- ) { var square = this.squares[ i ]; //check. 処理は尾のみを対象とする if( square.type == 0 ) continue; square.cnt++; //check. if( square.cnt >= square.cntmax ) { this.squares.splice( i, 1 ); } } this.draw(); } App.prototype.draw = function() { /* draw()はとても長くなった。 1. 画面に描く各四角形の4頂点の座標計算 2. 奥から手前の順になるようにソート 3. 描画 */ //1. 画面に描く各四角形の4頂点の座標計算 for( var j = 0; j < this.squares.length; j++ ) { var square = this.squares[ j ]; var is0 = square == this.square0; //square0はすい星の尾ではなく本体 square.jusin = xyz( 0, 0, 0); square.tensC = new Array(); for( var i = 0; i < square.ref.tens.length; i++ ) { var refTen = square.ref.tens[ i ]; //四角形を構成する1つの点について var x = refTen[ 0 ]; var y = refTen[ 1 ]; var z = refTen[ 2 ]; //以降この x, y, z に様々な計算(5種)を加える //1. 四角形の大きさ var a = square.type == 0 ? 1 : ( 1 / ( square.cnt * 0.75 ) ); //大きさの遅延 x *= square.scale * a; y *= square.scale * a; z *= square.scale * a; //2. 四角形の初期状態 自転 (最初の設定上の自転回転) var res = rotate1( x, z, square.rotation.y ); x = res.X; z = res.Y; var res = rotate1( z, y, square.rotation.x ); z = res.X; y = res.Y; var res = rotate1( x, y, square.rotation.z ); x = res.X; y = res.Y; //3. アニメ「自転」 (初期状態にアニメ分の回転を加える) var animate = this.animates[ 1 ]; //面が自転 //x軸回転 var res = rotate2( animate.pos.z, animate.pos.y, z, y, animate.rotation.x ); z = res.X; y = res.Y; //y軸回転 var res = rotate2( animate.pos.x, animate.pos.z, x, z, animate.rotation.y ); x = res.X; z = res.Y; //z軸回転 var res = rotate2( animate.pos.x, animate.pos.y, x, y, animate.rotation.z ); x = res.X; y = res.Y; //4. 四角形の初期状態 位置 (最初の設定上の位置) x += square.pos.x; y += square.pos.y; z += square.pos.z; //5. アニメ「公転」 各四角形がグルグル回る var animate; var a; if( square.type == 0 && square != this.square0 ) { //一枚のピンク色四角形 animate = this.animates[ 2 ]; //公転 逆 a = 0; } else { //その他水色の四角形 animate = this.animates[ 0 ]; //公転 var b = .15; //2個目の距離 a = ( is0 ? 0 : ( b + square.cnt * 0.075 ) ); //距離の遅延 } //x軸回転 var rotation = animate.rotation.x - ( animate.rotation.x != 0 ? a : 0 ); var res = rotate2( animate.pos.z, animate.pos.y, z, y, rotation ); z = res.X; y = res.Y; //y軸回転 var rotation = animate.rotation.y - ( animate.rotation.y != 0 ? a : 0 ); var res = rotate2( animate.pos.x, animate.pos.z, x, z, rotation ); x = res.X; z = res.Y; //z軸回転 var rotation = animate.rotation.z - ( animate.rotation.z != 0 ? a : 0 ); var res = rotate2( animate.pos.x, animate.pos.y, x, y, rotation ); x = res.X; y = res.Y; //以上の様子をカメラはどんな姿勢で捉えるか //カメラの位置 x -= this.camera.pos.x; y -= this.camera.pos.y; z -= this.camera.pos.z; //カメラの自転 var res = rotate1( x, z, -this.camera.rotation.y ); x = res.X; z = res.Y; var res = rotate1( z, y, -this.camera.rotation.x ); z = res.X; y = res.Y; var res = rotate1( x, y, -this.camera.rotation.z ); x = res.X; y = res.Y; //四角形の中心座標(重心)を平均値で求める 途中 square.jusin.x += x; square.jusin.y += y; square.jusin.z += z; //以上が3次元について、以下は2次元について var h = x * ( this.camera.s / z ); var v = -y * ( this.camera.s / z ); h *= this.camera.zoom; v *= this.camera.zoom; //この for i 文の目的がこれ square.tensC[ i ] = { x : x, y : y, z : z, h : h, v : v, }; }//for i //四角形の中心座標(重心)を平均値で求める 完了 square.jusin.x /= 4; square.jusin.y /= 4; square.jusin.z /= 4; //check. canvasを他のHTMLの手前に描くか奥に描くか if( square.jusin.z > this.animates[ 0 ].pos.z || isMouseOver( this.canvasEL ) ) { //奥へ this.canvasEL.style.zIndex = -1; } else { //手前へ this.canvasEL.style.zIndex = 255; } }//for j //2. 奥から手前の順になるようにソート (画家のアルゴリズム) this.squares.sort( function( a, b ) { return ( a.jusin.z < b.jusin.z ) - ( a.jusin.z > b.jusin.z ); } ); //3. 描画 with( this.canvas ) { clearRect( 0, 0, this.canvasW, this.canvasH ); save(); translate( this.canvasEL.width, this.canvasEL.height ); //画面をCSSで2倍に引き伸ばししているので、 //canvasELのHTML属性のサイズが、画面の中心を表す。 //画面の中心を 0 ,0 の原点とする。 for( var j = 0; j < this.squares.length; j++ ) { var square = this.squares[ j ]; beginPath(); for( var i = 0; i < square.tensC.length; i++ ) { var tenC = square.tensC[ i ]; if( i == 0 ) { moveTo( tenC.h, tenC.v ); } else { lineTo( tenC.h, tenC.v ); } } closePath(); if( square.type == 0 ) { //type 0: 尾ではない 1:尾である //すい星本体か、ピンクの四角形か fillStyle = square == this.square0 ? rgb( 0,255,192 ) : rgb( 255,128,192 ); } else { //すい星の尾 cntが大きいほど緑色 fillStyle = rgb( 0, 255, 192 * ( 1 / ( square.cnt * .5 ) ) ); } fill(); } restore(); //画面の中心を左上に戻す } } function isMouseOver( element ) { var r = element.getBoundingClientRect(); var res = mouseX >= r.left && mouseX <= r.left + r.width && mouseY >= r.top && mouseY <= r.top + r.height; return res; } function xyz( x, y, z ) { //便利関数 return { x : x, y : y, z : z }; } function rgb( r, g, b ) { //便利関数 return "rgb(" + r + "," + g + "," + b + ")" } function rotate1( x, y, theta2 ) { //数学関数 //0,0を原点として回転 var theta1 = Math.atan2( y, x ); var hankei = Math.sqrt( x * x + y * y ); var rx = Math.cos( theta1 + theta2 ) * hankei; var ry = Math.sin( theta1 + theta2 ) * hankei; return { X : rx, Y : ry }; } function rotate2( cx, cy, x, y, theta2 ) { //数学関数 //cx,cyを原点として回転 x -= cx; y -= cy; var res = rotate1( x, y, theta2 ); res.X += cx; res.Y += cy; return res; } function objcopy( obj ) { //オブジェクトを簡易コピーする var res; if( obj instanceof Array ) { res = new Array(); for( var i = 0; i < obj.length; i++ ) { if( typeof obj[ i ] == "object" ) { res[ i ] = objcopy( obj[ i ] ); } else { res[ i ] = obj[ i ]; } } } else { res = new Object(); for( var name in obj ) { if( typeof obj[ name ] == "object" ) { res[ name ] = objcopy( obj[ name ] ); } else { res[ name ] = obj[ name ]; } } } return res; } function printObj( obj ) { //デバッグ用 現在未使用 for( var name in obj ) { if( typeof obj[ name ] == "object" ) { console.log( name ); printObj( obj[ name ] ); } else { console.log( name, obj[ name ] ); } } } function mark( x, y, canvas ) { //デバッグ用 現在未使用 canvas.save(); canvas.fillRect( x, y, 4, 4 ); canvas.restore(); }