/*
【説明】
このファイルを 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();
}