/* どうなっているのか? 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.systemStart( parentEL ); //systemStartは最後にonloadx2を呼んでいる } }; //ページ読み込み完了で開始 //---画像プリロード indexjs.images = { "red" : "20180901-indexJS/imgs/red.png", "green" : "20180901-indexJS/imgs/green.png", "blue" : "20180901-indexJS/imgs/blue.png", "mountainLeft" : "20180901-indexJS/imgs/mountainLeft.png", "title" : "20180901-indexJS/imgs/title.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.systemStart = function( parentEL ) { /* 固定的な内容。 */ console.log( "systemStart()" ); this.speedstep = [ 10, 100, 200, 500 ]; this.timerMS = this.speedstep[ 1 ]; //(※正直言うと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 = true; 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.programStart(); }//systemStart App.prototype.onresizex = function( e ) { console.log( "onresizex()" ); /* systemStart()で呼ばれている リサイズすると呼ばれる */ //リサイズされた親要素に合わせて、サイズ変更 var wa = this.parentEL; var waRect = wa.getBoundingClientRect(); this.canvasW = Math.round( waRect.width ); //最初のh2タグに合わせる var htags = document.getElementsByTagName( "h2" ); if( htags.length > 0 ) { this.canvasH = Math.round( htags[ 0 ].getBoundingClientRect().top - wa.getBoundingClientRect().top ) - 16; } 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 ); } if( e ) this.draw(); }//onresizex //---以上 変更しない部分 //---以下 変更する部分 目的のソフトウェア App.prototype.programStart = function() { /* 基本的にはここから書く */ console.log( "programStart()" ); d3_onloadx(); this.start(); onkeydown = function( e ) { console.log( e.which ); indexjs.restart(); }; }; //--- タイマ制御系 App.prototype.start = function() { console.log( "start()", this.timerMS ); this.draw(); this.timerID = setInterval( this.run.bind( this ), this.timerMS ); }; App.prototype.stop = function() { if( this.timerID ) { console.log( "stop()" ); clearInterval( this.timerID ); this.timerID = null; } }; App.prototype.reset = function() { this.stop(); this.start(); }; App.prototype.restart = function() { this.stop(); this.programStart(); }; allstop = false; constMax = 5; allcount = 0; App.prototype.run = function() { allcount++; for( var i = models.length - 1; i >= 0; i-- ) { var model = models[ i ]; model.kaitenH += -.05; model.kaitenV += .025; // if( allcount > 300 ) { indexjs.restart(); return; } //check. if( model.animateFLG == false ) continue; model.animateValue += 1; //check. if( model.animateValue > model.animateValueMax ) { model.animateFLG = false; if( allstop ) { continue; } var array = splitMen( model, model.mens[ 0 ] ); for( var j = 0; j < array.length; j++ ) { var tmpmodel = array[ j ]; setAnimate( tmpmodel ); } cnt = 4; if( models.length == Math.pow(2,cnt*2+1)-1 ) { allstop = true; continue; } } } this.draw(); }; function setAnimate( object ) { object.hideproc = false; object.animateFLG = true; } App.prototype.draw = function() { var cc = this.cc; d3_draw(); }; //---extend //https://qiita.com/Layzie/items/465e715dae14e2f601de function is(type, obj) { var clas = Object.prototype.toString.call(obj).slice(8, -1); return obj !== undefined && obj !== null && clas === type; } console.logO = function() { var res = ""; for( var j = 0; j < arguments.length; j++ ) { var arg = arguments[ j ]; var st = ""; if( arg instanceof Object ) { for( var name in arg ) { st += name + ":" + arg[ name ] + ", "; } } else { st = arg; } res += " " + st; } console.log( res ); } console.logA = function() { var res = ""; for( var j = 0; j < arguments.length; j++ ) { var arg = arguments[ j ]; var st = ""; if( arg instanceof Object ) { for( var i = 0; i < arg.length; i++ ) { st += "[" + i + "] " + arg[ i ] + ", "; } } else { st = arg; } res += " " + st; } console.log( res ); } //値に注目して、配列にその要素があるかどうか function arrayIndexOf2( array, element ) { if( element instanceof Array ) { //要素が配列のとき for( var i = 0; i < array.length; i++ ) { var res = true; for( var j = 0; j < element.length; j++ ) if( element[ j ] != array[ i ][ j ] ) { res = false; break; } if( res ) return i; } } else if( element instanceof Object ) { //要素がオブジェクトのとき for( var i = 0; i < array.length; i++ ) { var res = true; for( var n in element ) if( element[ n ] != array[ i ][ n ] ) { res = false; break; } if( res ) return i; } } else { //要素が値のとき return array.indexOf( element ); } return -1; } //面を分割する function splitMen( model, men ) { var array = new Array(); var calcTens = new Array(); var seek1 = model.tens.length; for( var j = 0; j < men.tenIDXs.length; j++ ) { var tenIDX = men.tenIDXs[ j ]; var ten = model.tens[ tenIDX ]; var x = ten.x; var y = ten.y; var z = ten.z; //scale倍 x *= model.scale; y *= model.scale; z *= model.scale; //anim u = model.getJusin(); n = toNorm( { x:u.x, y:u.y, z:u.z } ); x += model.animateValue * n.x; y += model.animateValue * n.y; z += model.animateValue * n.z; x /= model.scale; y /= model.scale; z /= model.scale; var newTen = { x:x, y:y, z:z }; calcTens.push( newTen ); model.tens.push( newTen ); } /* calcTens calcTens calcTens calcTens tmpTen tmpTen tmpTen tmpTen jusin */ var seek2 = model.tens.length; var jusin = xyz( 0, 0, 0 ); var tmpTenIdxies = new Array(); //分割に使用する新点を作成 for( var i = 0; i < calcTens.length; i++ ) { var ten = calcTens[ i ]; //面の重心計算1 jusin.x += ten.x; jusin.y += ten.y; jusin.z += ten.z; //2点間の中点 var bakTen = calcTens[ i == 0 ? 3 : ( i - 1 ) ]; var x = ( ten.x + bakTen.x ) / 2; var y = ( ten.y + bakTen.y ) / 2; var z = ( ten.z + bakTen.z ) / 2; var newTen = new xyz( x, y, z ); tmpTenIdxies.push( model.tens.length ); model.tens.push( newTen ); } //面の重心計算2 jusin.x /= men.tenIDXs.length; jusin.y /= men.tenIDXs.length; jusin.z /= men.tenIDXs.length; var jusinSeek = model.tens.length; model.tens.push( jusin ); //元の面は削除 men.display = false; //新しい面を作成 for( var i = 0; i < men.tenIDXs.length; i++ ) { var tenIDX = men.tenIDXs[ i ]; var newMen = new Men( [ tmpTenIdxies[ i ], seek1 + i, tmpTenIdxies[ i == 3 ? 0 : ( i + 1 ) ], jusinSeek, ] ); //men.children.push( newMen ); newModel = floatMen( model, newMen ); newModel.animateValue = 0; newModel.animateValueMax = model.animateValue + constMax; array.push( newModel ); } return array; } //面を独立させる function floatMen( model, men ) { men.isFloat = true; //面が使っている点を独立させる newmodel = new Model(); newmodel.name = "men"; newmodel.mens[ 0 ] = new Men(); for( var i = 0; i < men.tenIDXs.length; i++ ) { var tenIDX = men.tenIDXs[ i ]; var ten = objcopy( model.tens[ tenIDX ] ); var newTenIDX = newmodel.tens.length; newmodel.tens.push( ten ); newmodel.mens[ 0 ].tenIDXs[ i ] = newTenIDX; } newmodel.scale = model.scale; newmodel.posX = model.posX; newmodel.posY = model.posY; newmodel.posZ = model.posZ; newmodel.kaitenH = model.kaitenH; newmodel.kaitenV = model.kaitenV; newmodel.parent = model; if( model.mens.indexOf( men ) > -1 ) model.mens.splice( model.mens.indexOf( men ), 1 ); models.push( newmodel ); return newmodel; } function objcopy( object ) { if( object instanceof Object ) { var newobject = new Array(); for( var n in object ) { newobject[ n ] = objcopy( object[ n ] ); } return newobject; } else if( object instanceof Array ) { var newarray = new Array(); for( var i = 0; i < object.length; i++ ) { newarray[ i ] = objcopy( object[ i ] ); } return newarray; } else { return object; } } //参照を切り離す function independentFromMaster( model ) { model.tens = objcopy( model.tens ); model.mens = objcopy( model.mens ); } function Men( tenIDXs ) { this.tenIDXs = tenIDXs == null ? new Array : tenIDXs; this.children = new Array(); this.display = true; this.isFloat = false; } //---3DCG function $( id ) { return document.getElementById( id ); } var HereDocument = /\/\*\s*([^]*?)\s*\*\//; var canvasEL, canvas; function d3_onloadx() { canvasEL = indexjs.canvasEL; canvas = canvasEL.getContext( '2d' ); screenW = canvasEL.width; screenH = canvasEL.height; colorselect = Math.floor( Math.random() * 6 ); allcount = 0; allstop = false; //マスターモデル masters = new Object(); masters.cube = new Object(); //正六面体の頂点(中心を原点としたときの位置) masters.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: 左上 奥 ]; masters.cube.mens = [ new Men( [ 3, 2, 1, 0 ] ), //0: 向かって正面 new Men( [ 1, 2, 6, 5 ] ), //1: 向かって右面 new Men( [ 0, 4, 7, 3 ] ), //2: 向かって左面 new Men( [ 7, 6, 2, 3 ] ), //3: 向かって上面 new Men( [ 0, 1, 5, 4 ] ), //4: 向かって下面 new Men( [ 5, 6, 7, 4 ] ), //5: 向かって背面 ]; //※各面の点の並びは時計回り //正六面体の拡大 masters.cube.scale = 10; //正六面体の位置 masters.cube.posX = 0; masters.cube.posY = 0; masters.cube.posZ = 80; //正六面体の回転 masters.cube.kaitenH = 0; masters.cube.kaitenV = 0; //正六面体のベース色 masters.cube.baseColor = { r : 196, g : 196, b : 0 }; //リンクモデル models = new Array(); //リンク1 赤 var model = new Model( masters.cube ); model.baseColor = { r : 196, g : 48, b : 48 }; model.kaitenH = .3; model.kaitenV = -.5; model.name = "red"; models.push( model ); cam = new Object(); //望遠レンズなら200、普通のカメラなら50(レンズサイズまたは焦点距離という) cam.s = 300; cam.zoom = 1; //14.25; //視点回転 cam.kaitenH = 0; cam.kaitenV = 0; //視点位置 cam.posX = 0; cam.posY = 0; cam.posZ = 0; /* //ある位置を注視しながら回転する var center = new Object(); center.x = models[ 0 ].posX; center.y = models[ 0 ].posY; center.z = models[ 0 ].posZ; doTarget( cam, center ); var kaitenH = -3.14 / 4; var kaitenV = -3.14 / 16 * 5; camDomeKaiten( cam, center, kaitenH, kaitenV ); */ //光源 kougen = new Object(); kougen.pos = xyz( 0, 0, 0 ); // model = models[ 0 ]; independentFromMaster( model ); list = new Array(); for( var i = 0; i < model.mens.length; i++ ) { list.push( model.mens[ i ] ); } for( var i = 0; i < list.length; i++ ) { floatMen( model, list[ i ] ); setAnimate( models[ models.length - 1 ] ); } d3_draw(); }//d3_onloadx function Model( master ) { if( master == null ) { this.tens = new Array(); this.mens = new Array(); this.scale = 1; this.posX = 0; this.posY = 0; this.posZ = 100; this.kaitenH = 0; this.kaitenV = 0; this.baseColor = [ { r : 127, g : 127, b : 255 }, { r : 127, g : 255, b : 127 }, { r : 255, g : 127, b : 127 }, { r : 127, g : 255, b : 255 }, { r : 255, g : 127, b : 255 }, { r : 255, g : 255, b : 127 }, ][ colorselect ]; } else { this.tens = master.tens; //参照 this.mens = master.mens; //参照 this.scale = master.scale; //コピー this.posX = master.posX; //コピー this.posY = master.posY; //コピー this.posZ = master.posZ; //コピー this.kaitenH = master.kaitenH; //コピー this.kaitenV = master.kaitenV; //コピー this.baseColor = objCpy( master.baseColor ); //コピー } this.children = new Array(); this.animateValue = 0; this.animateValueMax = constMax; this.animateFLG = false; this.hideproc = true; } Model.prototype.getJusin = function() { var jusin = xyz( 0, 0, 0 ); var count = 0; for( var i = 0; i < this.mens.length; i++ ) { var men = this.mens[ i ]; for( var j = 0; j < men.tenIDXs.length; j++ ) { var tenIDX = men.tenIDXs[ j ]; var ten = this.tens[ tenIDX ]; jusin.x += ten.x; jusin.y += ten.y; jusin.z += ten.z; count ++; } } jusin.x /= count; jusin.y /= count; jusin.z /= count; return jusin; } function d3_draw() { canvas.clearRect( 0, 0, indexjs.canvasW, indexjs.canvasH ); if( 0 ) { var sz = 32; canvas.strokeStyle = "lightgreen"; for( var y = 0; y < indexjs.canvasH; y += sz ) { canvas.lineWidth = y % 10 == 0 ? 2 : 1; canvas.beginPath(); canvas.moveTo( 0, y ); canvas.lineTo( indexjs.canvasW, y ); canvas.closePath(); canvas.stroke(); } for( var x = 0; x < indexjs.canvasW; x += sz ) { canvas.lineWidth = x % 10 == 0 ? 2 : 1; canvas.beginPath(); canvas.moveTo( x, 0 ); canvas.lineTo( x, indexjs.canvasH ); canvas.closePath(); canvas.stroke(); } } //各モデルについて for( var m = 0; m < models.length; m++ ) { var model = models[ m ]; model.tensC = new Array(); //頂点の位置を計算 for( var i = 0; i < model.tens.length; i++ ) { var x = model.tens[ i ].x; var y = model.tens[ i ].y; var z = model.tens[ i ].z; //scale倍 x *= model.scale; y *= model.scale; z *= model.scale; //anim u = model.getJusin(); n = toNorm( { x:u.x, y:u.y, z:u.z } ); x += model.animateValue * n.x; y += model.animateValue * n.y; z += model.animateValue * n.z; //回転 var res = kaiten( x, z, model.kaitenH ); x = res.X; z = res.Y; var res = kaiten( z, y, model.kaitenV ); z = res.X; y = res.Y; //位置へ移動 x += model.posX; y += model.posY; z += model.posZ; //カメラ移動 x += -cam.posX; y += -cam.posY; z += -cam.posZ; //カメラ回転 var res = kaiten( x, z, -cam.kaitenH ); x = res.X; z = res.Y; var res = kaiten( z, y, -cam.kaitenV ); z = res.X; y = res.Y; //3Dの座標を2Dの座標に変換する var h = x * ( cam.s / z ); var v = -y * ( cam.s / z ); //画面を引き延ばし h *= cam.zoom; v *= cam.zoom; //値を保管 model.tensC[ i ] = new Object(); model.tensC[ i ].x = x; model.tensC[ i ].y = y; model.tensC[ i ].z = z; model.tensC[ i ].h = h; model.tensC[ i ].v = v; }//for i }//for m //頂点の位置を計算済み //面の配列を作成する var allmens = new Array(); //各モデルについて var recursive = function( model, mens ) { for( var j = 0; j < mens.length; j++ ) { var men = mens[ j ]; recursive( model, men.children ); //check. if( men.display == false ) continue; var jusin; //陰面消去1と陰面消去2で使用する値 var housen; //陰面消去2と陰影処理で使用する値 var tmpTens = new Array(); for( var i = 0; i < men.tenIDXs.length; i++ ) tmpTens.push( model.tensC[ men.tenIDXs[ i ] ] ); jusin = getJusin( tmpTens ); housen = getHousen( tmpTens ); //check. 面が視点のほうを向いていないなら、その面は除去 //(陰面消去2 「法線ベクトル法」) // if( model.hideproc && ! checkVisibility( housen, jusin ) ) continue; //allmensへ追加する var theMen = new Object(); theMen.men = men; theMen.jusin = jusin; theMen.housen = housen; theMen.model = model; allmens.push( theMen ); }//for j }// for( var m = 0; m < models.length; m++ ) { var model = models[ m ]; recursive( model, model.mens ); }//for m //すべての面を奥から手前の順にソート //(陰面消去1 「画家のアルゴリズム」) allmens.sort( function( a, b ) { if( a.jusin.z > b.jusin.z ) return -1; else if( a.jusin.z < b.jusin.z ) return 1; else return 0; } ); //描画 canvas.save(); canvas.translate( indexjs.canvasW / 2, indexjs.canvasH / 2 ); test:for( var i = 0; i < allmens.length; i++ ) { var men = allmens[ i ].men; var model = allmens[ i ].model; var housen = allmens[ i ].housen; var jusin = allmens[ i ].jusin; //面を構成する点を順にたどる canvas.beginPath(); for( var j = 0; j < men.tenIDXs.length; j++ ) { var tenIDX = men.tenIDXs[ j ]; var ten = model.tensC[ tenIDX ]; //check. if( ten.z <= 0 ) { continue test; } var h = ten.h; var v = ten.v; //最初の点はmoveTo、続く点はlineTo if( j == 0 ) { canvas.moveTo( h, v ); } else { canvas.lineTo( h, v ); } } canvas.closePath(); //面の中を塗る //陰影処理(ランバート反射)を加える //kougen.posは原点から見た位置ですが、これを面から見た位置に直し、kougenPとします。 var kougenP = xyz( kougen.pos.x - jusin.x, kougen.pos.y - jusin.y, kougen.pos.z - jusin.z ); canvas.fillStyle = rgb2str( lambelt( toNorm( kougenP ), housen, model.baseColor ) ); canvas.fill(); //面の線を描く canvas.strokeStyle = "black"; canvas.stroke(); } canvas.restore(); var w = 320; var h = 18; canvas.font = h + "px 'MS Pゴシック'"; canvas.fillText( "3DCGモデルの面を繰り返し分割する", indexjs.canvasW - w, indexjs.canvasH - h, w ); } //---その他関数 function checkVisibility( housen, jusin ) { //視点の方向(eye) var sisen = objCpy( jusin ); sisen.x *= -1; sisen.y *= -1; sisen.z *= -1; //法線と視点方向の内積(※cosθ) var a = naiseki( housen, sisen ); return a > 0; } function kaiten( x, y, theta2 ) { /* 数学分野の汎用関数 原点を中心にして、回転する */ var theta1 = Math.atan2( y, x ); var hankei = Math.sqrt( x * x + y * y ); var kaitenX = Math.cos( theta1 + theta2 ) * hankei; var kaitenY = Math.sin( theta1 + theta2 ) * hankei; return { X : kaitenX, Y : kaitenY }; } function rgb2str( rgb ) { /* 便利目的の関数 */ return "RGB(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; } function objCpy( obj ) { /* 便利目的の関数 */ var res = new Object(); for( var name in obj ) { res[ name ] = obj[ name ]; } return res; } function xyz( x, y, z ) { /* 便利目的の関数 */ return { x : x, y : y, z : z }; } function getJusin( p ) { /* 数学分野の汎用関数 面を構成する各頂点の、x,y,z各成分の平均値を得る(重心) */ var res = xyz( 0, 0, 0 ); for( var 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 doTarget( cam, targetP ) { /* カメラの位置はそのままで、targetの方向を向かせる。 */ //カメラを0,0,0としたターゲットの位置 var tx = targetP.x - cam.posX; var ty = targetP.y - cam.posY; var tz = targetP.z - cam.posZ; //カメラから見たターゲットの角度 var kakudoTH = Math.atan2( tz, tx ); var kakudoTV = Math.atan2( ty, tz ); //カメラに反映 cam.kaitenH = kakudoTH - 1.57; //角度はxの方向から数えるのに対し、カメラはZ方向を見て回転0としているから-1.57 cam.kaitenV = kakudoTV; } function camDomeKaiten( cam, center, theta2H, theta2V ) { /* カメラを、ある位置を注視しながら回転させる */ //水平回転 if( theta2H != 0 ) { //水平面において、カメラの位置をcenterを中心にtheta2だけ加算回転、 var res = kaiten2( center.x, center.z, cam.posX, cam.posZ, theta2H ); cam.posX = res.X; cam.posZ = res.Y; cam.kaitenH += theta2H; } //垂直回転 if( theta2V != 0 ) { //check. 真上、真下になったら回転しない /* 縦角度の加算後が、真上(-1.57)や真下(1.57)を超えるなら、 加算後の縦角度が-1.57か1.57になるように加算値を変更する。 */ var v = Math.abs( Math.floor( ( cam.kaitenV + theta2V ) * 100 ) / 100 ); if( v >= 3.14 / 2 ) { theta2V = 3.14 / 2 * ( theta2V > 0 ? 1 : -1 ) - cam.kaitenV; //check. if( theta2V == 0 ) return; } //1. 水平回転分を回転なしへ戻す var kaitenHbak = cam.kaitenH; var res = kaiten2( center.x, center.z, cam.posX, cam.posZ, -cam.kaitenH ); cam.posX = res.X; cam.posZ = res.Y; cam.kaitenH = 0; //2. 垂直回転を行う var res = kaiten2( center.z, center.y, cam.posZ, cam.posY, theta2V ); cam.posZ = res.X; cam.posY = res.Y; cam.kaitenV += theta2V; //3. 水平回転を復元する var res = kaiten2( center.x, center.z, cam.posX, cam.posZ, kaitenHbak ); cam.posX = res.X; cam.posZ = res.Y; cam.kaitenH = kaitenHbak; }//if }//camDomeKaiten() function kaiten2( centerX, centerY, x, y, theta2 ) { /* 数学分野の汎用関数 ある場所を中心にして、回転する */ x -= centerX; y -= centerY; var theta1 = Math.atan2( y, x ); var hankei = Math.sqrt( x * x + y * y ); var kaitenX = Math.cos( theta1 + theta2 ) * hankei; var kaitenY = Math.sin( theta1 + theta2 ) * hankei; kaitenX += centerX; kaitenY += centerY; return { X : kaitenX, Y : kaitenY }; } function naiseki( v1, v2 ) { /* 数学分野の汎用関数 内積を得る */ var a = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; return a; } 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にした座標を返す(単位ベクトル化) (※陰面消去2では本当は必要ないがランバート反射で必要になったので用意した) */ 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 lambelt( housen, kougen, rgb ) { var cosTheta = naiseki( housen, kougen ); var directPer = 0.6; //直接光の割合 var ambientPer = 1 - directPer; //環境光の割合 //色成分をさらに直接光と環境光の割合で分割、直接光についてはcosTheta(0~1)でさらにしぼる var dirR = rgb.r * directPer * cosTheta; var ambR = rgb.r * ambientPer; var dirG = rgb.g * directPer * cosTheta; var ambG = rgb.g * ambientPer; var dirB = rgb.b * directPer * cosTheta; var ambB = rgb.b * ambientPer; //分割したものを再度合体(直接光分にcosThetaを掛けたかっただけ) var res = new Object(); res.r = Math.round( dirR + ambR ); res.g = Math.round( dirG + ambG ); res.b = Math.round( dirB + ambB ); return res; }