/* どうなっているのか? App() App.onloadx() htmlのonloadで呼ばれる。画像のプリロードからも呼ばれ、loadをカウントしてる。 App.onloadx1() htmlとimageのloadが完了したら呼ばれる。固定的な内容。onloadx2を呼ぶ。 App.onresizex() 固定的な内容。通常は変更しないと思う。 App.onloadx2() ★ここから書く★ App.start() stopをクリックした後再開するとき呼ばれる。 App.run() 書いたもの。消しても良い App.draw() 書いたもの。消しても良い 【説明】 このファイルを 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 などのライブラリは一切使用していません。 */ //---以下 変更しない部分 /* → relative, z-index=0; に変更される
STOPやSPEEDなどのリンク
自動作成される 自動作成される
*/ var indexjs = new App(); App.prototype.onloadx = function( e ) { this.onloadxCount++; console.log( this.onloadxCount + " / " + this.onloadxCountMax ); //check. if( this.onloadxCount == this.onloadxCountMax ) { var parentEL = document.getElementById( "whiteareaID" ); this.onloadx1( parentEL ); //onloadx1は最後にonloadx2を呼んでいる } }; //ページ読み込み完了で開始 //---画像 indexjs.images = { "yuyake" : "20180801-indexJS/imgs/Sheet1.png", "gaito" : "20180801-indexJS/imgs/Sheet2.png", } indexjs.onloadxCount = 0; indexjs.onloadxCountMax = Object.keys( indexjs.images ).length + 1; for( var name in indexjs.images ) { var src = indexjs.images[ name ]; indexjs.images[ name ] = new Image(); indexjs.images[ name ].onload = indexjs.onloadx.bind( indexjs ); indexjs.images[ name ].src = src; } addEventListener( "load", indexjs.onloadx.bind( indexjs ) ); function App() { //Appクラスのコンストラクタ this.name = "test"; } App.prototype.onloadx1 = function( parentEL ) { console.log( "onloadx1" ); //(※正直言うとcanvasのサイズについて多少混乱中…) //check. if( ! parentEL ) parentEL = document.getElementsByTagName( "body" )[ 0 ]; //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 = ""; this.swEL.innerHTML += "STOP
"; this.swEL.innerHTML += 'SPEED'; //CANVAS要素 親要素の上部に配置 this.canvasEL = document.createElement( "canvas" ); this.parentEL.appendChild( this.canvasEL ); this.canvasEL.setAttribute( "id", "indexjs_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.lowmode = false; this.cc = this.canvasEL.getContext( "2d" ); this.onresizex(); //resize設定 addEventListener( "resize", ( function( e ) { this.onresizex( e ); } ).bind( this ), false ); //canvasが画面外に出たらCPUパワーを抑えるために停止する処理 addEventListener( "scroll", function( e ) { var scrollEL = document.documentElement ? document.documentElement : document.body; var r = indexjs.cc.canvas.getBoundingClientRect(); if( scrollEL.scrollTop + r.top + r.height / 2 < 0 ) indexjs.stop(); else if( ! indexjs.timerID ) indexjs.start(); }, false ); this.onloadx2(); }//onloadx1 App.prototype.onresizex = function( e ) { //リサイズされた親要素に合わせて、サイズ変更 var wa = this.parentEL; var waRect = wa.getBoundingClientRect(); this.canvasW = waRect.width; //最初のh2タグに合わせる var htags = document.getElementsByTagName( "h2" ); if( htags.length > 0 ) { this.canvasH = htags[ 0 ].getBoundingClientRect().top - wa.getBoundingClientRect().top - 128; } else { this.canvasH = 480; } this.canvasEL.style.width = this.canvasW + "px"; this.canvasEL.style.height = this.canvasH + "px"; this.canvasEL.setAttribute( "width", this.canvasW ); this.canvasEL.setAttribute( "height", this.canvasH ); //true にすると解像度を下げる。false は通常。 if( this.lowmode ) { 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.cc.scale( 1 / pixelsize, 1 / pixelsize ); } //ビルのシルエット作成 this.building = new Array(); var x = 0; with( this ) { var takasaMax = canvasH - 8; var takasaMin = canvasH - 64; var habaMax = 32; var habaMin = 8; this.building.push( { x : canvasW, y : canvasH } ); this.building.push( { x : 0, y : canvasH } ); while( 1 ) { var takasa = Math.random() * ( takasaMax - takasaMin ) + takasaMin; this.building.push( { x : x, y : takasa } ); var haba = Math.random() * ( habaMax - habaMin ) + habaMin; x += haba; //check. if( x >= canvasW ) { this.building.push( { x : canvasW, y : takasa } ); break; } this.building.push( { x : x, y : takasa } ); } } if( e ) this.draw(); }//onresizex //---以上 変更しない部分 //---以下 変更する部分 目的のソフトウェア App.prototype.onloadx2 = function() { console.log( "onloadx2" ); //like DirectX ///---allMaster this.allMaster = { type : "normal", name : "noname", tens : null, mens : null, fillStyle : "salmon", strokeStyle : "black", kaitenX : 0, kaitenY : 0, kaitenZ : 0, kaitenStep : .05, zm : 100, pos : { x : 0, y : 0, z : 200, }, parent : null, visibility : true, }; ///---cubeMaster this.cubeMaster = objcopy( this.allMaster ); with( this.cubeMaster ) { type = "cube"; tens = [ { x : -1, y : +1, z : -1 }, //手前 左上点 0 { x : +1, y : +1, z : -1 }, //手前 右上点 1 { x : +1, y : -1, z : -1 }, //手前 右下点 2 { x : -1, y : -1, z : -1 }, //手前 左下点 3 { x : -1, y : +1, z : +1 }, //奥 左上点 4 { x : +1, y : +1, z : +1 }, //奥 右上点 5 { x : +1, y : -1, z : +1 }, //奥 右下点 6 { x : -1, y : -1, z : +1 }, //奥 左下点 7 ]; mens = [ [ 0, 1, 2, 3 ], //前面 [ 1, 5, 6, 2 ], //右面 [ 4, 0, 3, 7 ], //左面 [ 4, 5, 1, 0 ], //上面 [ 3, 2, 6, 7 ], //下面 [ 5, 4, 7, 6 ], //背面 ]; fillStyle = ""; }; ///---hanabiraMaster this.hanabiraMaster = objcopy( this.allMaster ); with( this.hanabiraMaster ) { type = "hanabira"; tens = [ { x : 0, y : 4, z : 0 }, //花びらスリット部 { x : 1, y : 7, z : 0 }, { x : 3, y : 5, z : 0 }, { x : 4, y : 1, z : 0 }, //花びら右端 { x : 3, y : -3, z : 0 }, { x : 0, y : -6, z : 0 }, //花びら根元部 { x : -3, y : -3, z : 0 }, { x : -4, y : 1, z : 0 }, //花びら左端 { x : -3, y : 5, z : 0 }, { x : -1, y : 7, z : 0 }, ]; mens = [ [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], //上記頂点をこの順で描いて面とする ]; zm = .5; strokeStyle = ""; } ///---areaMaster this.areaMaster = objcopy( this.cubeMaster ); with( this.areaMaster ) { zm = 1; visibility = false; //エリアを示すワイヤフレームは最初見えない pos.x = 30; pos.z = 450; fillStyle = ""; strokeStyle = "pink"; //ワイヤフレームの色(後で上書きされている) kaitenZ = 3.14 / 16; //左にやや傾ける kaitenStep = .025; } //独自メンバを新規作成 this.areaMaster.hankei = 300; //花びらが配置される筒状の半径 this.areaMaster.takasa = this.canvasH / 1; //筒状の高さ this.areaMaster.holeHankeiRate = .5; //ドーナツ状にくりぬく分の割合 this.areaMaster.holeHankei = null; this.areaMaster.countOfHanabira = 100; //花びらの数(後で上書きされている) this.areaMaster.hanabira = null; //---areaの作成 this.areas = new Array(); switch( 0 ) { case 0: //ノーマル this.bgmode = 2; //夕焼け this.timerMS = 50; this.cam = new Object(); this.cam.s = 62; //焦点距離 this.cam.zm = 14.25; //引き伸ばし //エリアは2個 for( var i = 0; i < 2; i++ ) { this.areas[ i ] = objcopy( this.areaMaster ); with( this.areas[ i ] ) { switch( i ) { case 0: countOfHanabira = 130; pos.x = 140; kaitenX = 0.174; strokeStyle = "white"; //ワイヤフレーム表示時のワイヤ色 break; case 1: //エリア2は花びらの数20%、全体の傾き20% countOfHanabira = Math.round( this.areas[ 0 ].countOfHanabira * .2 ); kaitenZ = this.areas[ 0 ].kaitenZ - .2; strokeStyle = "magenta"; break; } } } break; }//switch var areaChange = function() { //エリアのサイズが変更されたら、頂点も変更する。 for( var j = 0; j < this.areas.length; j++ ) { var area = this.areas[ j ]; area.holeHankei = area.hankei * area.holeHankeiRate; area.tens = objcopy( this.areaMaster.tens ); //初期化 for( var i = 0; i < area.tens.length; i++ ) { var ten = area.tens[ i ]; ten.x *= area.hankei; ten.y *= area.takasa / 2; ten.z *= area.hankei; } } }.bind( this ); areaChange(); onkeydown = function( e ) { //イベントだが、this は indexjs になっている //---キー入力 switch( e.which ) { case 32: //space stop時コマ送り e.preventDefault(); e.stopPropagation(); //check. if( this.timerID ) break; this.run(); return false; default: } switch( String.fromCharCode( e.which + 32 ) ) { case "q": this.cam.s++; break; //焦点距離増減 case "a": this.cam.s--; break; case "o": //背景切り替え this.bgmode += 1; if( this.bgmode >= this.bgs.length ) this.bgmode = 0; break; case "z": this.lowmode = !this.lowmode; this.onresizex(); break; //背景切り替え case "l": //設定値出力 for( var name in this.cam ) console.log( "this.cam:", name, this.cam[ name ] ); break; default: console.log( "ascii:", String.fromCharCode( e.which + 32 ), "e.which:", e.which ); } for( var i = 0; i < this.areas.length; i++ ) { var area = this.areas[ i ]; switch( String.fromCharCode( e.which + 32 ) ) { case "p": area.visibility = ! area.visibility; break;//エリア枠の表示 case "w": area.kaitenX += area.kaitenStep; break; //エリア枠をX軸回転 case "s": area.kaitenX -= area.kaitenStep; break; case "e": area.kaitenY += area.kaitenStep; break; //エリア枠をY軸回転 case "d": area.kaitenY -= area.kaitenStep; break; case "r": area.kaitenZ += area.kaitenStep; break; //エリア枠をZ軸回転 case "f": area.kaitenZ -= area.kaitenStep; break; case "t": area.hankei += 10; areaChange(); this.reset(); break; //エリア枠の幅を増 case "g": area.hankei -= 10; areaChange(); this.reset(); break; case "y": area.takasa += 10; areaChange(); this.reset(); break; //エリア枠の高さを増減 case "h": area.takasa -= 10; areaChange(); this.reset(); break; case "i": area.pos.z += 10; this.reset(); break; //ijkmの十字キーで、エリア枠を水平方向に移動 case "j": area.pos.x -= 10; this.reset(); break; case "k": area.pos.x += 10; this.reset(); break; case "m": area.pos.z -= 10; this.reset(); break; case "l": //設定値出力 console.log( "areas[ " + i + " ].hankei:", area.hankei ); console.log( "areas[ " + i + " ].takasa:", area.takasa ); for( var name in area ) console.log( "areas[ " + i + " ]:", name, area[ name ] ); for( var name in area.pos ) console.log( "areas[ " + i + " ].pos:", name, area.pos[ name ] ); break; default: }//switch }//for this.draw(); }.bind( this ); //---背景描画設定 this.bgs = new Array(); this.bgs[ 0 ] = function() { //透明(白)背景 this.cc.clearRect( 0,0, this.canvasW, this.canvasH ); }.bind( this ); this.bgs[ 1 ] = function() { //青空と草原 with( this.cc ) { var horizontal = .75; var skyHeight = this.canvasH * horizontal; var roundHeight = this.canvasH - skyHeight; fillStyle = "blue"; fillRect( 0,0, this.canvasW, skyHeight ); fillStyle = "green"; fillRect( 0,skyHeight, this.canvasW, roundHeight ); if( 0 ) { //地平線の白 var w = 16; var grad = this.cc.createLinearGradient( 0, skyHeight - w, 0, skyHeight + w ); grad.addColorStop( 0, 'rgba( 0, 0, 0, 0 )' ); grad.addColorStop( 0.5, 'rgba( 255,255,255, .5 )' ); grad.addColorStop( 1, 'rgba( 0, 0, 0, 0 )' ); this.cc.fillStyle = grad; this.cc.fillRect( 0, skyHeight - w, this.canvasW, skyHeight + w ); } }//with }.bind( this );//function this.bgs[ 2 ] = function() { //ゆうやけ this.cc.clearRect( 0,0, this.canvasW, this.canvasH ); this.cc.drawImage( this.images.yuyake, 0,0,this.canvasW, this.canvasH ); with( this.cc ) { beginPath(); for( var i = 0; i < this.building.length; i++ ) { var p = this.building[ i ]; if( i == 0 ) moveTo( p.x, p.y ); else lineTo( p.x, p.y ); } closePath(); fillStyle = "black"; fill(); } }.bind( this ); //function this.bgs[ 3 ] = function() { //夜桜 var bottom = 16; this.cc.fillStyle = "black"; this.cc.fillRect( 0,0, this.canvasW, this.canvasH - bottom ); //街灯の電球の位置を基準にする var denkyuX = this.canvasW / 5; var denkyuY = this.canvasH / 4; //円形グラデ 街灯の照らし var r0 = 18; var x0 = denkyuX; //電球付近から var y0 = denkyuY; //電球の明るいのが及ぶ部分へ var r1 = 150; var x1 = x0 + Math.floor( Math.sqrt( ( r1 * r1 ) / 2 ) ) - Math.ceil( Math.sqrt( ( r0 * r0 ) / 2 ) ); var y1 = y0 + Math.floor( Math.sqrt( ( r1 * r1 ) / 2 ) ) - Math.ceil( Math.sqrt( ( r0 * r0 ) / 2 ) ); var grad = this.cc.createRadialGradient(x0, y0, r0, x1, y1, r1); grad.addColorStop( 0, 'white' ); grad.addColorStop( 0.5, 'rgba(0,0,0,0)' ); grad.addColorStop( 1, 'black' ); this.cc.fillStyle = grad; this.cc.fillRect(0,0, this.canvasW,this.canvasH); //街灯から離れた暗がり(後描画) this.drawNightForeground = function() { var r2 = 8; var x2 = denkyuX; //この円から var y2 = denkyuY; var r3 = this.canvasW / 2; var x3 = x2 + this.canvasW / 3.5; //次の円 var y3 = y2; var grad = this.cc.createRadialGradient(x2, y2, r2, x3, y3, r3); grad.addColorStop( 0, 'rgba(0,0,0,0)' ); grad.addColorStop( .75, 'rgba(0,0,0,0)' ); grad.addColorStop( 1, 'rgba(0,0,0,.95)' ); this.cc.fillStyle = grad; this.cc.fillRect(0,0, this.canvasW,this.canvasH); //線形グラデ 下部 ドキュメントの白へのグラデ this.cc.beginPath(); var grad = this.cc.createLinearGradient( 0, this.canvasH - bottom, 0, this.canvasH ); grad.addColorStop( 0, 'rgba(0,0,0,0)' ); grad.addColorStop( 1, 'white' ); this.cc.fillStyle = grad; this.cc.fillRect( 0, this.canvasH - bottom, this.canvasW, bottom ); //夜桜です、という表示 this.cc.fillStyle = "white"; this.cc.fillText( "夜桜", this.canvasW - 48, this.canvasH - 32 ); };//function //街灯画像 this.cc.drawImage( this.images.gaito, denkyuX - 80, denkyuY - 22 ); }.bind( this );//function this.onloadx3(); }; App.prototype.onloadx3 = function() { this.models = new Array(); //---hanabiraの作成 for( var j = 0; j < this.areas.length; j++ ) { var area = this.areas[ j ]; area.hanabiras = new Array(); for( var i = 0; i < area.countOfHanabira; i++ ) { var hanabira = objcopy( this.hanabiraMaster ); if( 1 ) { //正六面体状に配置 var ry = Math.floor( Math.random() * area.takasa ) - area.takasa / 2; var hankei = area.hankei; var holeHankei = hankei * 0.5; var ringW = hankei - holeHankei; var a = Math.floor( Math.random() * 3 ); var b = Math.floor( Math.random() * 4 ); var rx = Math.floor( Math.random() * ringW ) + ( (a==1 || a== 2) ? (holeHankei) : 0 ); var rz = Math.floor( Math.random() * ringW ) + ( (a==0 || a== 1) ? (holeHankei) : 0 ); rx *= ( b == 1 || b == 2 ) ? -1 : 1; rz *= ( b == 2 || b == 3 ) ? -1 : 1; } else { //円筒形状に配置 var baumkuchenW = area.hankei - area.holeHankei; //バームクーヘンの太さ var ry = Math.round( Math.random() * area.takasa ) - area.takasa / 2; //バームクーヘン状にちりばめる var theta = Math.random() * 6.28; var hankei2 = Math.random() * baumkuchenW + area.holeHankei; var rx = Math.cos( theta ) * hankei2 var rz = Math.sin( theta ) * hankei2; } hanabira.pos = { x : area.pos.x + rx, y : area.pos.y + ry, z : area.pos.z + rz, }; hanabira.kaitenY = Math.random() * 6.28; hanabira.kaitenX = Math.random() * 6.28; hanabira.parent = area; hanabira.fillStyle = j == 0 ? "rgb(255,240,240)" : "rgb(255,210,210)"; area.hanabiras.push( hanabira ); } this.models.push( area ); this.models = this.models.concat( area.hanabiras ); } this.draw(); this.start(); } App.prototype.start = function() { this.timerID = setInterval( this.run.bind( this ), this.timerMS ); }; App.prototype.stop = function() { if( this.timerID ) { clearInterval( this.timerID ); this.timerID = null; } } /* this.hanabirasを this.areas[ 0 ].hanabirasにしたところ。 花びら作成をエリア対応に。 エリアごとの花びら数を調整。 花びら描画をエリア対応に。 作成した花びらをmodelsへ追加するところエリア対応。 */ App.prototype.reset = function() { this.stop(); this.onloadx3(); } App.prototype.run = function() { //それぞれの回転を進める for( var j = 0; j < this.areas.length; j++ ) { this.areas[ j ].kaitenY += this.areas[ j ].kaitenStep; } for( var i = 0; i < this.areas[ 0 ].hanabiras.length; i++ ) { var hanabira = this.areas[ 0 ].hanabiras[ i ]; hanabira.kaitenY += .05; hanabira.kaitenX += .05; } this.draw(); }; App.prototype.draw = function() { //背景描画 this.bgs[ this.bgmode ](); this.cc.save(); this.cc.translate( this.canvasW / 2, this.canvasH / 2 ); this.cc.scale( 1, -1 ); //都合により頂点計算部分を関数へ分離 var tenkeisan = function( model ) { //check. if( ! model.visibility ) return; model.tensC = new Array(); //modelは、はなびらと、エリア枠も含まれる。(汎用的) for( var i = 0; i < model.tens.length; i++ ) { var ten = model.tens[ i ]; var x = ten.x; var y = ten.y; var z = ten.z; x *= model.zm; y *= model.zm; z *= model.zm; //自転 //kaitenY var k = kaiten( x, z, model.kaitenY ); x = k.X; z = k.Y; //kaitenX var k = kaiten( z, y, model.kaitenX ); z = k.X; y = k.Y; //kaitenZ var k = kaiten( x, y, model.kaitenZ ); x = k.X; y = k.Y; x += model.pos.x; y += model.pos.y; z += model.pos.z; //エリアが回転すると、エリアを親とする花びらたちも回転する if( model.parent != null ) { var p = model.parent; //kaitenY var k = kaiten2( p.pos.x, p.pos.z, x, z, p.kaitenY ); x = k.X; z = k.Y; //kaitenX var k = kaiten2( p.pos.z, p.pos.y, z, y, p.kaitenX ); z = k.X; y = k.Y; //kaitenZ var k = kaiten2( p.pos.x, p.pos.y, x, y, p.kaitenZ ); x = k.X; y = k.Y; } //3Dを2D化 var h = x * ( this.cam.s / z ) * this.cam.zm; var v = y * ( this.cam.s / z ) * this.cam.zm; var tenC = { x : x, y : y, z : z, h : h, v : v, }; model.tensC[ i ] = tenC; } }.bind( this );//function //頂点を計算済みにする for( var j = 0; j < this.models.length; j++ ) { tenkeisan( this.models[ j ] ); } forK:for( var k = 0; k < this.models.length; k++ ) { var model = this.models[ k ]; //check. if( ! model.visibility ) continue; for( var j = 0; j < model.mens.length; j++ ) { var men = model.mens[ j ]; var overZ = false; var points = new Array(); this.cc.beginPath(); for( var i = 0; i < men.length; i++ ) { var tenIdx = men[ i ]; var tenC = model.tensC[ tenIdx ]; //check. 視点の背後に触れた if( tenC.z <= 0 ) { overZ = true; break; } var h = tenC.h; var v = tenC.v; if( i == 0 ) this.cc.moveTo( h, v ); else this.cc.lineTo( h, v ); points.push( tenC ); } this.cc.closePath(); //check. 視点の背後に触れたものは描かない。 if( overZ ) continue; //check. 線状に見える角度の花びらを正面向きに直す。視線と面の向きの内積で、線状に見えてるのかどうか判断 if( model.type == "hanabira" ) { var housen = getHousen( points ); var sisen = toNorm( { x : -points[ 1 ].x, y : -points[ 1 ].y, z : -points[ 1 ].z, } ); var cosTheta = naiseki( housen, sisen ); var limit = .3; //線状に見えるとする幅 if( cosTheta > -limit && cosTheta < limit ) { model.kaitenY = Math.random() * 6.28; model.kaitenX = Math.random() * 6.28; //このモデルを新しい角度で計算し直し tenkeisan( model ); k--; continue forK; } } //描く if( model.fillStyle ) { this.cc.fillStyle = model.fillStyle; this.cc.fill(); } if( model.strokeStyle ) { this.cc.strokeStyle = model.strokeStyle; this.cc.stroke(); } }//for }//for this.cc.restore(); if( this.bgmode == 3 ) { this.drawNightForeground(); } }; function kaiten( 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 kaiten2( cx, cy, x, y, theta2 ) { //数学関数 //cx,cyを原点として回転 x -= cx; y -= cy; var res = kaiten( 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 if( obj instanceof Object ) { res = new Object(); for( var name in obj ) { if( typeof obj[ name ] == "object" ) { res[ name ] = objcopy( obj[ name ] ); } else { res[ name ] = obj[ name ]; } } } else { res = null; } return res; } function getHousen( p ) { /* 数学分野の汎用関数 法線を得る */ var x1 = p[ 0 ].x, y1 = p[ 0 ].y, z1 = p[ 0 ].z; var x2 = p[ 1 ].x, y2 = p[ 1 ].y, z2 = p[ 1 ].z; var x3 = p[ 2 ].x, y3 = p[ 2 ].y, z3 = p[ 2 ].z; //法線 var 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 toNorm( p ) { /* 数学分野の汎用関数 原点から座標pまでの距離を1にした座標を返す(単位ベクトル化) */ var len = Math.sqrt( p.x * p.x + p.y * p.y + p.z * p.z ); var res = new Object(); res.x = p.x / len; res.y = p.y / len; res.z = p.z / len; return res; } function naiseki( v1, v2 ) { /* 数学分野の汎用関数 内積を得る */ var a = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; return a; }