addEventListener( "load", function() { console.log( "onload" ); //check. if( typeof apps === "undefined" ) apps = new Array(); app = new App20200909( document.getElementById( "canvas202008indexjs" ) ); apps.push( app ); }, false ); class App20200909 { //---App constructor constructor( canvas ) { this.cc = canvas.getContext( "2d", { alpha : false, desynchronized : true, } ); this.cfg = { bunkatusu : 5, //立方体のマス目 smooth : 1, //ラインのスムーズさ(マス目間を何ステップで移動するか) lineLength : 7, //ラインの長さ lineAlgo : 4, //ラインアニメアルゴリズム //1 始点、終点ともにランダム //2 始点ランダム、終点はその反対位置 //3 2で、始点を90度変えて2個 //4 3で、始点をさらに90度変えて3個 timerMs : 150, //全体の速度 setIntervalの引数 } this.pixelWidth = 1; this.pixelHeight = 1; //サイドプログラム //リサイズ時対応 let onresize = function( e ) { console.log( "onresize" ); let canvas = this.cc.canvas; this.canvasRect = canvas.getBoundingClientRect(); //枠サイズ分 let cs = getComputedStyle( canvas ); let array = [ cs.borderLeftWidth.replace( /px/, "" ) - 0, cs.borderRightWidth.replace( /px/, "" ) - 0, cs.borderTopWidth.replace( /px/, "" ) - 0, cs.borderBottomWidth.replace( /px/, "" ) - 0, ]; this.canvasRect.width -= array[ 0 ] + array[ 1 ]; this.canvasRect.height -= array[ 2 ] + array[ 3 ]; //論理座標(プログラム上の座標)を変えずに、画面の粗さだけを変える this.screenWidth = canvas.parentNode.getBoundingClientRect().width; this.screenHeight = 512; canvas.width = this.screenWidth; canvas.height = this.screenHeight; //pixel canvas.width /= this.pixelWidth; canvas.height /= this.pixelHeight; canvas.style.width = this.screenWidth + "px"; canvas.style.height = this.screenHeight + "px"; //繰り返し呼ばれるdraw()で使用する値を前計算しておく。(効果は不明) this.scaleArg1 = 1 / this.pixelWidth; this.scaleArg2 = 1 / this.pixelHeight; this.transArg1 = this.screenWidth / 2; this.transArg2 = this.screenHeight / 2; //場所かくほ let p = this.cc.canvas.parentNode; p.style.paddingTop = canvas.style.height; this.draw( this.cc ); }.bind( this ); //onresize() //メインプログラム this.models = new Array(); this.cube = new Model( "blue", "normal" ); this.cube.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 ]; this.cube.mens = [ [ 0, 1, 2, 3 ], //正面 [ 5, 4, 7, 6 ], //背面 [ 0, 3, 7, 4 ], //左面 [ 5, 6, 2, 1 ], //右面 [ 1, 0, 4, 5 ], //天面 [ 2, 6, 7, 3 ], //底面 ]; this.cube.size = 200; this.cube.pos = [ 0, 0, 1000 ]; this.cube.fillStyle = "#440022"; this.cube.strokeStyle = "#440022"; this.cube.rotationXAdd = 0.005; this.cube.rotationYAdd = 0.02; this.cube.revolutionXAdd = 0.005; this.cube.revolutionYAdd = 0.02; this.models.push( this.cube ); //---cube.frame this.cube.frame = function() { this.rotation[ 0 ] += this.rotationXAdd; //X this.rotation[ 1 ] += this.rotationYAdd; //Y this.area = 100; this.step = this.area / 40; // this.pos[ 0 ] += this.direction * this.step; //check. if( this.pos[ 0 ] >= this.area || this.pos[ 0 ] <= -this.area ) this.direction *= -1; } this.cube.direction = 1; console.log( "models.length", this.models.length ); this.cam = new Object(); this.cam.s = 50; this.cam.zoom = 14.25; //---マス目状のポイントを作成。 this.mat1 = new Matrix( "mat1", this.cube ); this.mat1.direction = this.cube.direction; this.mat1.bunkatu( this.cfg.bunkatusu ); this.models.push( this.mat1 ); this.lines = new Array(); this.models_normal = new Array(); this.models_matrix = new Array(); this.models_line = new Array(); /* let tests = [ [ 12, 80, 108 ], [ 217, 258 ], [ 500, 380, 555 ], [ 300, 325 ], [ 500, 520 ], [ 202, 425 ], [ 370, 497 ] ]; */ //サイドプログラム //実行時表示調整 this.cc.canvas.style.imageRendering = "crisp-edges"; // this.cc.canvas.style.zIndex = -1; this.cc.canvas.style.position = "absolute"; this.cc.canvas.style.left = 0; this.cc.canvas.style.top = 0; // this.cc.canvas.style.border = "dashed 3px red"; onresize( null ); //サイドプログラム //ページスクロールでストップするしくみ //どの要素を対象にしますか this.target = this.cc.canvas; //要素が何%見えたらアクティブにしますか this.targetPer = 0.5; this.isActive = false; addEventListener( "scroll", this.onscrollx.bind( this ) ); window.addEventListener( "resize", onresize, false ); this.test(); }//constructor //---test async test() { await this.test1(); //lines await this.test2(); //mini cubes this.test4(); //green matrix this.cube.display = false; await this.delay( 500 ); await this.test3(); //mini cubes moving console.log( "OK" ); } async test4() { for( let i = 0; i < this.miniCubes.length; i++ ) { let miniCube = this.miniCubes[ i ]; miniCube.strokeStyle = "#220011"; // await this.delay( 30 ); } } async test3() { for( let i = 0; i < this.miniCubes.length; i++ ) { let miniCube = this.miniCubes[ i ]; let speed = 9; miniCube.posAdd = [ Math.random() * speed - speed / 2, Math.random() * speed - speed / 2, Math.random() * speed - speed / 2, ]; miniCube.rotationXAdd = ( Math.random() - 0.5 ) / 300, miniCube.rotationYAdd = ( Math.random() - 0.5 ) / 300, miniCube.strokeStyle = "pink"; await this.delay( 100 ); } } delay( ms ) { return new Promise( function( tellOk ) { setTimeout( tellOk, ms ); } ); } mathRotateBy( cx, cy, x, y, theta2 ) { x -= cx; y -= cy; let hankei = Math.sqrt( x * x + y * y ); let theta1 = Math.atan2( y, x ); x = Math.cos( theta1 + theta2 ) * hankei; y = Math.sin( theta1 + theta2 ) * hankei; x += cx; y += cy; return { x : x, y : y }; } //---test2() 小cubeをたくさん作成 test2() { //小cubeをたくさん作成 this.models.length = 0; this.models.push( this.cube ); this.miniCubes = new Array(); let bksu = this.cfg.bunkatusu; let kankaku = 2 / bksu; //2は本当はcubeのtensから計算すべき let khf = kankaku / 2; //Kankaku HarF console.log( bksu ); let model; let cx = this.cube.pos[ 0 ]; let cy = this.cube.pos[ 1 ]; let cz = this.cube.pos[ 2 ]; for( let z = -1; z < 1; z += kankaku ) { for( let y = -1; y < 1; y += kankaku ) { for( let x = -1; x < 1; x += kankaku ) { model = new Model( "test", "normal" ); model.tens = this.cube.tens; model.mens = this.cube.mens; model.size = this.cube.size / bksu; model.pos = [ ( x + khf ) * this.cube.size + this.cube.pos[ 0 ], ( y + khf ) * this.cube.size + this.cube.pos[ 1 ], ( z + khf ) * this.cube.size + this.cube.pos[ 2 ], ]; let r, theta; //公転 X軸回転 theta = this.cube.rotation[ 0 ]; r = this.mathRotateBy( cz, cy, model.pos[ 2 ], model.pos[ 1 ], theta ); model.pos[ 2 ] = r.x; model.pos[ 1 ] = r.y; //公転 Y軸回転 theta = this.cube.rotation[ 1 ]; r = this.mathRotateBy( cx, cz, model.pos[ 0 ], model.pos[ 2 ], theta ); model.pos[ 0 ] = r.x; model.pos[ 2 ] = r.y; //小cubeたちは、this.cube の中心を中心にして回転 model.revolutionX = { cz : this.cube.pos[ 2 ], cy : this.cube.pos[ 1 ], add : this.cube.rotationXAdd, theta : 0, }; model.revolutionY = { cx : this.cube.pos[ 0 ], cz : this.cube.pos[ 2 ], add : this.cube.rotationYAdd, theta : 0, }; model.rotation = this.objcpy( this.cube.rotation ); model.fillStyle = "#440022"; // model.strokeStyle = "#220011"; model.strokeStyle = "#440022"; this.models.push( model ); this.miniCubes.push( model ); }//for x }//for y }//for z this.furiwake(); return new Promise( function( tellOk ) { this.tellOk = tellOk; //これをAとする。以降コメントでAと言うのはここのこと。 }.bind( this ) ); }//test2() //---test1() ライン作成 test1() { //---ライン作成 let mat1 = this.mat1; let tests = new Array(); let spnum, sp, epnum, r; switch( this.cfg.lineAlgo ) { case 5: //case4について、epを反対位置ではなく、90度位置へ変更。 for( let k = 0; k < 3; k++ ) { let key; //spについて if( k == 0 ) { //最初の始点はランダムに決める spnum = Math.floor( Math.random() * mat1.points.length ); } else if( k == 1 ) { //2回目の始点は最初の始点を90度回転させた場所にする(交差させたい) r = this.mathRotate( sp.x, sp.y, Math.PI / 2 ); key = Math.round( r.x * 10 ) / 10 + "," + Math.round( r.y * 10 ) / 10 + "," + sp.z; spnum = mat1.points.indexOf( mat1.pointsHash[ key ] ); } else { //3回目の始点は2回目の始点を別の座標系で90度回転させた場所にする(交差させたい) r = this.mathRotate( sp.x, sp.z, Math.PI / 2 ); key = Math.round( r.x * 10 ) / 10 + "," + sp.y + "," + Math.round( r.y * 10 ) / 10; spnum = mat1.points.indexOf( mat1.pointsHash[ key ] ); } sp = mat1.points[ spnum ]; //epについて r = this.mathRotate( sp.x * -1, sp.y * -1, Math.PI / 2 ); key = Math.round( r.x * 10 ) / 10 + "," + Math.round( r.y * 10 ) / 10 + "," + sp.z * -1; epnum = mat1.points.indexOf( mat1.pointsHash[ key ] ); //そのsp, epを結ぶラインを複数作成 for( let i = 0; i < 5; i++ ) { //ライン数 let root = new Array(); root.push( spnum ); for( let j = 0; j < 1; j++ ) { //経由点数 let keiyu; //重複しないように決定 do { keiyu = Math.floor( Math.random() * mat1.tens.length ); } while( keiyu == spnum || keiyu == epnum ); root.push( keiyu ); } root.push( epnum ); //debug. console.log( k, i, root.join( "," ) ); tests.push( root ); } } break; case 4: //case3について、3つの場所から開始するように変更。 for( let k = 0; k < 3; k++ ) { let key; //spについて if( k == 0 ) { //最初の始点はランダムに決める spnum = Math.floor( Math.random() * mat1.points.length ); } else if( k == 1 ) { //2回目の始点は最初の始点を90度回転させた場所にする(交差させたい) r = this.mathRotate( sp.x, sp.y, Math.PI / 2 ); key = Math.round( r.x * 10 ) / 10 + "," + Math.round( r.y * 10 ) / 10 + "," + sp.z; spnum = mat1.points.indexOf( mat1.pointsHash[ key ] ); } else { //3回目の始点は2回目の始点を別の座標系で90度回転させた場所にする(交差させたい) r = this.mathRotate( sp.x, sp.z, Math.PI / 2 ); key = Math.round( r.x * 10 ) / 10 + "," + sp.y + "," + Math.round( r.y * 10 ) / 10; spnum = mat1.points.indexOf( mat1.pointsHash[ key ] ); } sp = mat1.points[ spnum ]; //epについて key = sp.x * -1 + "," + sp.y * -1 + "," + sp.z * -1; epnum = mat1.points.indexOf( mat1.pointsHash[ key ] ); //そのsp, epを結ぶラインを複数作成 for( let i = 0; i < 5; i++ ) { //ライン数 let root = new Array(); root.push( spnum ); for( let j = 0; j < 1; j++ ) { //経由点数 let keiyu; //重複しないように決定 do { keiyu = Math.floor( Math.random() * mat1.tens.length ); } while( keiyu == spnum || keiyu == epnum ); root.push( keiyu ); } root.push( epnum ); //debug. console.log( k, i, root.join( "," ) ); tests.push( root ); } } break; case 3: //case2について、2つの場所から開始するように変更。 for( let k = 0; k < 2; k++ ) { let key; //spについて if( k == 0 ) { //最初の始点はランダムに決める spnum = Math.floor( Math.random() * mat1.points.length ); } else { //次の始点は最初の始点を90度回転させた場所にする(交差させたい) let r = this.mathRotate( sp.x, sp.y, Math.PI / 2 ); key = Math.round( r.x * 10 ) / 10 + "," + Math.round( r.y * 10 ) / 10 + "," + sp.z; spnum = mat1.points.indexOf( mat1.pointsHash[ key ] ); } sp = mat1.points[ spnum ]; //epについて key = sp.x * -1 + "," + sp.y * -1 + "," + sp.z * -1; epnum = mat1.points.indexOf( mat1.pointsHash[ key ] ); //そのsp, epを結ぶラインを複数作成 for( let i = 0; i < 7; i++ ) { //ライン数 let root = new Array(); root.push( spnum ); for( let j = 0; j < 1; j++ ) { //経由点数 let keiyu; //重複しないように決定 do { keiyu = Math.floor( Math.random() * mat1.tens.length ); } while( keiyu == spnum || keiyu == epnum ); root.push( keiyu ); } root.push( epnum ); tests.push( root ); } } break; case 2: //ランダムな点を始点、その反対位置を終点とし、ランダムな1点を経由する。 spnum = Math.floor( Math.random() * mat1.points.length ); sp = mat1.points[ spnum ]; let key = sp.x * -1 + "," + sp.y * -1 + "," + sp.z * -1; epnum = mat1.points.indexOf( mat1.pointsHash[ key ] ); for( let i = 0; i < 15; i++ ) { //ライン数 let root = new Array(); root.push( spnum ); for( let j = 0; j < 1; j++ ) { //経由点数 let keiyu; //重複しないように決定 do { keiyu = Math.floor( Math.random() * mat1.tens.length ); } while( keiyu == spnum || keiyu == epnum ); root.push( keiyu ); } root.push( epnum ); tests.push( root ); } break; case 1: //すべての点をランダムに決定 for( let i = 0; i < 10; i++ ) { //ライン数 let root = new Array(); for( let j = 0; j < 2; j++ ) { //経由点数 let keiyu; //重複しないように決定 do { keiyu = Math.floor( Math.random() * mat1.tens.length ); } while( root.indexOf( keiyu ) > -1 ); root.push( keiyu ); } tests.push( root ); } break; } for( let i = 0; i < tests.length; i++ ) { //番号の配列を、Pointオブジェクトの配列に直す。 let keiyus = tests[ i ].map( idx => mat1.points[ idx ] ); //debug. // console.log( i, keiyus.join( "," ) ); let root = this.makeRoot( keiyus ); let line = new Line( "pink", root, this ); this.lines.push( line ); line.model.size = mat1.size; line.model.pos = mat1.pos; line.model.rotation = mat1.rotation; this.models.push( line.model ); } this.furiwake(); //開始 this.onscrollx(); return new Promise( function( tellOk ) { this.tellOk = tellOk; //これをAとする。以降コメントでAと言うのはここのこと。 }.bind( this ) ); }//test1() //---furiwake() furiwake() { //---3Dモデルを種類で振り分け this.models_normal.length = 0; this.models_matrix.length = 0; this.models_line.length = 0; for( let i = 0; i < this.models.length; i++ ) { let model = this.models[ i ]; switch( model.type ) { case "normal": this.models_normal.push( model ); break; case "matrix": this.models_matrix.push( model ); break; case "line": this.models_line.push( model ); break; } } //debug. console.log( "models_normal.length", this.models_normal.length ); console.log( "models_matrix.length", this.models_matrix.length ); console.log( "models_line.length", this.models_line.length ); } //---App onscrollx onscrollx( e ) { //ブラウザスクロール位置による、開始停止制御 var rect = this.target.getBoundingClientRect(); var targetTopP = ( rect.top ) + ( rect.height * this.targetPer ); var targetBottomP = ( rect.top + rect.height ) - ( rect.height * this.targetPer ); var windowTop = 0; var windowBottom = window.innerHeight; var overTheTop = targetBottomP < windowTop; //ウィンドウ上方へ隠れた var overTheBottom = targetTopP > windowBottom; //ウィンドウ下方へ隠れた var isVisible = ! overTheTop && ! overTheBottom; if( ! this.isActive && isVisible ) { this.isActive = true; this.start(); } else if( this.isActive && ! isVisible ) { this.isActive = false; this.stop(); } } //---App makeRoot makeRoot( keiyus ) { //keiyusは各経由点をあらわす。 //以降で、経由点間を埋める点を作成していく。 let root = [ keiyus[ 0 ] ]; //rootには出発点がまず入る。 let cursor = keiyus[ 0 ]; //各経由点について for( let j = 1; j < keiyus.length; j++ ) { let keiyu = keiyus[ j ]; //カーソル点から経由点のあいだについてルートを決めていく //経由点に到達するまで、ルートを決め続ける let limitbreak = 100; do { let naisekies = new Array(); //カーソル点から見た経由点の位置 let keiyu_n = { x : keiyu.x - cursor.x, y : keiyu.y - cursor.y, z : keiyu.z - cursor.z, }; //それを単位円の円周上の点にする。(単位ベクトル化) keiyu_n = this.norm( keiyu_n ); //カーソル点のまわりの各近接点について let kinsetu; for( let i = 0; i < cursor.kinsetus.length; i++ ) { kinsetu = cursor.kinsetus[ i ]; //カーソル点から見た近接点の位置 let kinsetu_n = { x : kinsetu.x - cursor.x, y : kinsetu.y - cursor.y, z : kinsetu.z - cursor.z, }; //それを単位円の円周上の点にする。(単位ベクトル化) kinsetu_n = this.norm( kinsetu_n ); naisekies.push( this.naiseki( keiyu_n, kinsetu_n ) ); } kinsetu = cursor.kinsetus[ naisekies.indexOf( Math.max( ...naisekies ) ) ]; //check. 既に通った(循環する) if( root.indexOf( kinsetu ) > -1 ) { break; } //その近接点をルートとして使用する。 root.push( kinsetu ); //その近接点へカーソルを進め、経由点へ到達するまで繰り返す cursor = kinsetu; } while( cursor != keiyu && limitbreak-- > 0 ); if( limitbreak == -1 ) { alert( "limitbreak" ); } //そのカーソルから次の経由点を目指す }//for keiyus // console.log( root.map( p => p.idx ).join( " -> " ) ); return root; }//makeRoot() naiseki( v1, v2 ) { /* 数学分野の汎用関数 内積を得る */ var a = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; return a; }//naiseki() //v1,v2は単位ベクトルであること norm( 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; }//norm() //---App start start() { this.timerID = setInterval( this.frame.bind( this ), this.cfg.timerMs ); // this.frame(); } //---App stop stop() { clearInterval( this.timerID ); } //---App frame frame() { for( let i = 0; i < this.models.length; i++ ) { let model = this.models[ i ]; if( model.deleteFlg ) { this.models.splice( i, 1 ); let idx; if( idx = this.models_normal.indexOf( model ) ) { this.models_normal.splice( idx, 1 ); } console.log( "deleted", this.models.length ); i--; continue; } if( model.posAdd ) { model.pos[ 0 ] += model.posAdd[ 0 ]; model.pos[ 1 ] += model.posAdd[ 1 ]; model.pos[ 2 ] += model.posAdd[ 2 ]; } } //3Dモデルの自転 if( 1 ) { for( let i = 0; i < this.models.length; i++ ) { let model = this.models[ i ]; model.rotation[ 0 ] += model.rotationXAdd; //X model.rotation[ 1 ] += model.rotationYAdd; //Y } } //3Dモデルの公転 for( let i = 0; i < this.models.length; i++ ) { let model = this.models[ i ]; if( model.revolutionX ) { model.revolutionX.theta += model.revolutionX.add; } if( model.revolutionY ) { model.revolutionY.theta += model.revolutionY.add; } } //各Lineのframe実行 let allstop = true; for( let i = 0; i < this.lines.length; i++ ) { let line = this.lines[ i ]; line.frame(); //check. allstop = allstop && line.stopFlg; } //check. Lineはすべて停止したか? if( allstop ) { /* //再開する for( let i = 0; i < this.lines.length; i++ ) { let line = this.lines[ i ]; line.endCursor.init(); line.frontCursor.init(); line.init(); } */ } this.draw( this.cc ); if( allstop ) { this.tellOk(); //前述コメントのAのtellOKがこれ。 } }//frame //---App draw draw( cc ) { //点を計算 for( let m = 0; m < this.models.length; m++ ) { let model = this.models[ m ]; if( model.type == "line" ) { for( let n = 0; n < model.tens.length; n++ ) { let ten = model.tens[ n ]; } } model.tensC = new Array(); for( let i = 0; i < model.tens.length; i++ ) { let ten = model.tens[ i ]; let x = ten[ 0 ]; let y = ten[ 1 ]; let z = ten[ 2 ]; x *= model.size; y *= model.size; z *= model.size; //自転 let a; //X軸回転 a = this.mathRotate( z, y, model.rotation[ 0 ] ); z = a.x; y = a.y; //Y軸回転 a = this.mathRotate( x, z, model.rotation[ 1 ] ); x = a.x; z = a.y; x += model.pos[ 0 ]; y += model.pos[ 1 ]; z += model.pos[ 2 ]; //公転 if( model.revolutionX ) { let rev = model.revolutionX; a = this.mathRotateBy( rev.cz, rev.cy, z, y, rev.theta ); z = a.x; y = a.y; } if( model.revolutionY ) { let rev = model.revolutionY; a = this.mathRotateBy( rev.cx, rev.cz, x, z, rev.theta ); x = a.x; z = a.y; } //check. if( this.miniCubes && z <= model.size ) { model.deleteFlg = true; } let h = x * ( this.cam.s / z ) * this.cam.zoom; let v = y * ( this.cam.s / z ) * this.cam.zoom; model.tensC[ i ] = { x : x, y : y, z : z, h : h, v : v, } }//for tens }//for models //面を計算 let allmens = new Array(); for( let m = 0; m < this.models_normal.length; m++ ) { let model = this.models_normal[ m ]; for( let i = 0; i < model.mens.length; i++ ) { let men = model.mens[ i ]; //check. 面が視点のほうを向いていないなら、その面は除去 let tmpTens = new Array(); for( let j = 0; j < men.length; j++ ) tmpTens.push( model.tensC[ men[ j ] ] ); men.visibility = this.checkVisibility( tmpTens ); if( ! men.visibility ) continue; let aveZ = 0; for( let j = 0; j < men.length; j++ ) { let tenIdx = men[ j ]; let tenC = model.tensC[ tenIdx ]; aveZ += tenC.z; } aveZ /= men.length; let menC = { men : men, model : model, aveZ : aveZ, } allmens.push( menC ); }//for model.mens }//for models_normal //すべての面を奥から手前の順にソート //(陰面消去1 「画家のアルゴリズム」) allmens.sort( function( a, b ) { if( a.aveZ > b.aveZ ) return -1; else if( a.aveZ < b.aveZ ) return 1; else return 0; } ); //---App draw - 描画 cc.clearRect( 0, 0, this.screenWidth, this.screenHeight ); cc.save(); cc.scale( this.scaleArg1, this.scaleArg2 ); cc.translate( this.transArg1, this.transArg2 ); // cc.globalAlpha = 0.5; //ポリゴンを描画 for( let i = 0; i < allmens.length; i++ ) { let menC = allmens[ i ]; let model = menC.model; //check. if( ! model.display ) continue; let tensC = model.tensC; let men = menC.men; //パス作成 cc.beginPath(); cc.moveTo( tensC[ men[ 0 ] ].h, tensC[ men[ 0 ] ].v ); for( let j = 1; j < men.length; j++ ) cc.lineTo( tensC[ men[ j ] ].h, tensC[ men[ j ] ].v ); cc.closePath(); //パスを面として塗る cc.fillStyle = model.fillStyle; cc.fill(); //パスを枠として描く // cc.save(); // cc.shadowColor = "orange"; // cc.shadowOffsetX = 0; // cc.shadowOffsetY = 0; // cc.shadowBlur = 15; cc.strokeStyle = model.strokeStyle; cc.stroke(); // cc.stroke(); // cc.restore(); }//for allmens //マトリクス(マス目)を描画 if( 0 ) { for( let i = 0; i < this.models_matrix.length; i++ ) { let model = this.models_matrix[ i ]; for( let j = 0; j < model.tens.length; j++ ) { let ten = model.tens[ j ]; let tenC = model.tensC[ j ]; //check. その点は描画されるか let menlist = ten[ 4 ]; let visibility = false; for( let k = 0; k < menlist.length; k++ ) visibility = visibility || menlist[ k ].visibility; if( ! visibility ) continue; let p = ten[ 3 ]; cc.beginPath(); cc.arc( tenC.h, tenC.v, 2, 0, 6.28, false ); cc.strokeStyle = "yellow"; cc.closePath(); cc.stroke(); //idx if( 0 ) { cc.globalAlpha = 1; cc.font = "8px ''"; cc.fillStyle = "black"; cc.fillText( p.idx, tenC.h + 2, tenC.v - 2 ); } }//for model.tens }//for models_matrix }//if 1,0 //---ラインを描画 cc.shadowOffsetX = 0; cc.shadowOffsetY = 0; cc.shadowBlur = 10; for( let i = 0; i < this.models_line.length; i++ ) { let model = this.models_line[ i ]; //check. if( model.tensC.length == 0 ) continue; cc.strokeStyle = model.strokeStyle; cc.shadowColor = model.shadowColor; cc.beginPath(); let tenC = model.tensC[ 0 ]; cc.moveTo( tenC.h, tenC.v ); for( let k = 0; k < 3; k++ ) { //濃くするため折り返して描画 //kが奇数のときは逆方向、偶数のときは順方向 if( k % 2 ) for( let j = model.tensC.length - 2; j >= 0; j-- ) cc.lineTo( model.tensC[ j ].h, model.tensC[ j ].v ); else for( let j = 1; j < model.tensC.length; j++ ) cc.lineTo( model.tensC[ j ].h, model.tensC[ j ].v ); } cc.stroke(); }//for models_line cc.restore(); }//draw() //---App checkVisibility checkVisibility( p ) { let x1 = p[ 0 ].x, y1 = p[ 0 ].y, z1 = p[ 0 ].z; let x2 = p[ 1 ].x, y2 = p[ 1 ].y, z2 = p[ 1 ].z; let x3 = p[ 2 ].x, y3 = p[ 2 ].y, z3 = p[ 2 ].z; //法線(housen) let hx = (y2-y1)*(z3-z2)-(z2-z1)*(y3-y2); let hy = (z2-z1)*(x3-x2)-(x2-x1)*(z3-z2); let hz = (x2-x1)*(y3-y2)-(y2-y1)*(x3-x2); //視点の方向(eye) let ex = 0, ey = 0, ez = 0; //重心(各成分の平均値)を計算している for( let i = 0; i < p.length; i++ ) { ex += p[ i ].x; ey += p[ i ].y; ez += p[ i ].z; } ex /= -p.length; ey /= -p.length; ez /= -p.length; //法線と視点方向の内積(※cosθ)が0より大きいなら、その面は見える return ex * hx + ey * hy + ez * hz > 0; }//checkVisibility() //---App mathRotate mathRotate( x, y, theta2 ) { let hankei = Math.sqrt( x * x + y * y ); let theta1 = Math.atan2( y, x ); return { x : Math.cos( theta1 + theta2 ) * hankei, y : Math.sin( theta1 + theta2 ) * hankei, }; }//mathRotate() }//App //---class Model class Model { constructor( name, type ) { this.name = name; this.type = type; this.tens = new Array(); this.mens = new Array(); this.pos = [ 0, 0, 0 ]; this.rotation = [ 0, 0, 0 ]; //自転 this.rotationXAdd = 0; this.rotationYAdd = 0; this.revolution = null; //公転 this.size = 100; this.display = true; this.strokeStyle = "black"; this.fillStyle = "white"; this.deleteFlg = false; } frame() { } } //---class Point class Point { constructor( idx, x, y, z ) { this.idx = idx; this.x = x; this.y = y; this.z = z; this.kinsetus = new Array(); } } class Cursor { //Cursor constructor( type, root, app ) { this.type = type; this.root = root; this.maxCount = app.cfg.smooth; this.init(); } //Cursor init() { this.x = this.root[ 0 ].x; this.y = this.root[ 0 ].y; this.z = this.root[ 0 ].z; this.count = this.maxCount; //最初に処理を実行させるために同じにする this.destinationPoint = this.root[ 0 ]; this.addX = 0; this.addY = 0; this.transitCount = 0; //ポイントを通過した回数 this.stopFlg = false; } //Cursor frame() { //check. if( this.stopFlg ) return; this.x += this.addX; this.y += this.addY; this.z += this.addZ; this.count++; } //Cursor checkDestination() { if( ! this.stopFlg && this.isDestination() ) { //check. 全ルート終了 if( this.transitCount == this.root.length ) { this.stopFlg = true; return; } //次の目的点 this.setDestination( this.root[ this.transitCount ++ ] ); } } //Cursor isDestination() { return this.count == this.maxCount; } //Cursor setDestination( point ) { this.destinationPoint = point; this.count = 0; this.lenX = this.destinationPoint.x - this.x; this.addX = this.lenX / this.maxCount; this.lenY = this.destinationPoint.y - this.y; this.addY = this.lenY / this.maxCount; this.lenZ = this.destinationPoint.z - this.z; this.addZ = this.lenZ / this.maxCount; } }//class Cursor //---class Line class Line { //Line constructor( shadowColor, root, app ) { this.root = root; this.app = app; this.startPoint = this.root[ 0 ]; this.frontCursor = new Cursor( "f", this.root, app ); this.endCursor = new Cursor( "e", this.root, app ); this.endCursorTiming = app.cfg.lineLength; this.init(); this.model = new Model( "line", "line" ); this.model.tens = new Array(); this.model.size = 200; this.model.pos = [ 0,0,0 ]; this.model.rotation = [ 0,0,0 ]; this.model.strokeStyle = "white"; this.model.shadowColor = shadowColor; }//constructor //Line init() { this.stopFlg = false; this.endCursor.stopFlg = true; this.endCursorTimingCount = 0; } //Line frame() { //check. endCursorの動作開始 if( this.endCursorTimingCount > -1 ) { //待ち時間満了したら if( this.endCursorTimingCount == this.endCursorTiming ) { //動作開始する this.endCursor.stopFlg = false; this.endCursorTimingCount = -1; //一度だけの処理とする } else { //待ち時間カウント進める this.endCursorTimingCount ++; } } //frontCursorについて this.frontCursor.checkDestination( this.root ); this.frontCursor.frame(); //endCursorについて if( this.endCursor ) { this.endCursor.checkDestination( this.root ); this.endCursor.frame(); } //Lineの停止は、両カーソルが停止したとき。 this.stopFlg = this.endCursor.stopFlg && this.frontCursor.stopFlg; //---Lineの3Dモデルを更新 let tens = this.model.tens; tens.length = 0; //線の末尾 tens.push( [ this.endCursor.x, this.endCursor.y, this.endCursor.z, ] ); //線の途中 let startIndex = this.root.indexOf( this.endCursor.destinationPoint ); let endIndex = this.root.indexOf( this.frontCursor.destinationPoint ); for( let i = startIndex; i < endIndex; i++ ) { let point = this.root[ i ]; tens.push( [ point.x, point.y, point.z, ] ); } //線の始点 tens.push( [ this.frontCursor.x, this.frontCursor.y, this.frontCursor.z, ] ); }//frame }//class Line //---class Matrix class Matrix extends Model { constructor( name, refModel ) { super( name, "matrix" ); this.size = this.objcpy( refModel.size ); this.pos = this.objcpy( refModel.pos ); this.frame = refModel.frame.bind( this ); this.rotation = refModel.rotation; this.refModel = refModel; this.kankaku = null; this.points = new Array(); this.pointsHash = new Object(); } //--- bunkatu bunkatu( bunkatusu ) { /* 3Dモデルの各面を分割数で分割した各点を返す。 注:立方体モデルのみ想定している。 */ this.points.length = 0; let tensHash = new Object(); //同じ点を重複して定義するのを防ぐため、Objectのキーを利用する。 let kankaku = null; let idx = 0; for( let i = 0; i < this.refModel.mens.length; i++ ) { let men = this.refModel.mens[ i ]; //正方形の各頂点をabcdとする。 let a = this.refModel.tens[ men[ 0 ] ]; let b = this.refModel.tens[ men[ 1 ] ]; let c = this.refModel.tens[ men[ 2 ] ]; let d = this.refModel.tens[ men[ 3 ] ]; //分析 let xallsame = a[ 0 ] == b[ 0 ] && c[ 0 ] == d[ 0 ] && a[ 0 ] == c[ 0 ]; let yallsame = a[ 1 ] == b[ 1 ] && c[ 1 ] == d[ 1 ] && a[ 1 ] == c[ 1 ]; let zallsame = a[ 2 ] == b[ 2 ] && c[ 2 ] == d[ 2 ] && a[ 2 ] == c[ 2 ]; //これから作成する点と点の間隔を、正面向きの面を例に取得する //(立方体の定義(前述)が正面向きの面からなので、kankakuは最初に定義される。問題ない) if( kankaku == null && zallsame ) { if( a[ 0 ] != b[ 0 ] ) kankaku = Math.abs( a[ 0 ] - b[ 0 ] ) / bunkatusu; else kankaku = Math.abs( a[ 0 ] - d[ 0 ] ) / bunkatusu; console.log( "kankaku", kankaku ); } console.log( i, xallsame, yallsame, zallsame ); let x, y, z; if( zallsame ) { let z = a[ 2 ]; let stepX, stepY; //ここにif文、本当は必要。下段と同じように。 stepX = ( b[ 0 ] - a[ 0 ] ) / bunkatusu; stepY = ( d[ 1 ] - a[ 1 ] ) / bunkatusu; for( let Y = 0; Y <= bunkatusu; Y ++ ) { for( let X = 0; X <= bunkatusu; X ++ ) { let x = Math.round( ( a[ 0 ] + X * stepX ) * 100 ) / 100; let y = Math.round( ( a[ 1 ] + Y * stepY ) * 100 ) / 100; let key = x + "," + y + "," + z; if( ! tensHash[ key ] ) { let point = new Point( idx++, x, y, z ); this.pointsHash[ key ] = point; tensHash[ key ] = [ x, y, z, point, new Array() ]; } tensHash[ key ][ 4 ].push( men ); } } } else if( yallsame ) { let y = a[ 1 ]; let stepX, stepZ; if( b[ 0 ] == a[ 0 ] ) { stepX = ( c[ 0 ] - b[ 0 ] ) / bunkatusu; stepZ = ( b[ 2 ] - a[ 2 ] ) / bunkatusu; } else { stepX = ( b[ 0 ] - a[ 0 ] ) / bunkatusu; stepZ = ( d[ 2 ] - a[ 2 ] ) / bunkatusu; } for( let Z = 0; Z <= bunkatusu; Z ++ ) { for( let X = 0; X <= bunkatusu; X ++ ) { let x = Math.round( ( a[ 0 ] + X * stepX ) * 100 ) / 100; let z = Math.round( ( a[ 2 ] + Z * stepZ ) * 100 ) / 100; let key = x + "," + y + "," + z; if( ! tensHash[ key ] ) { let point = new Point( idx++, x, y, z ); this.pointsHash[ key ] = point; tensHash[ key ] = [ x, y, z, point, new Array() ]; } tensHash[ key ][ 4 ].push( men ); } } } else if( xallsame ) { let x = a[ 0 ]; let stepZ, stepY; if( b[ 2 ] == a[ 2 ] ) { stepZ = ( c[ 2 ] - b[ 2 ] ) / bunkatusu; stepY = ( b[ 1 ] - a[ 1 ] ) / bunkatusu; } else { stepZ = ( b[ 2 ] - a[ 2 ] ) / bunkatusu; stepY = ( d[ 1 ] - a[ 1 ] ) / bunkatusu; } for( let Y = 0; Y <= bunkatusu; Y ++ ) { for( let Z = 0; Z <= bunkatusu; Z ++ ) { let z = Math.round( ( a[ 2 ] + Z * stepZ ) * 100 ) / 100; let y = Math.round( ( a[ 1 ] + Y * stepY ) * 100 ) / 100; let key = x + "," + y + "," + z; if( ! tensHash[ key ] ) { let point = new Point( idx++, x, y, z ); this.pointsHash[ key ] = point; tensHash[ key ] = [ x, y, z, point, new Array() ]; } tensHash[ key ][ 4 ].push( men ); } } }//if }//for mens this.tens = Object.values( tensHash ); this.points.length = 0; for( let i = 0; i < this.tens.length; i++ ) { let ten = this.tens[ i ]; let point = ten[ 3 ]; this.points.push( point ); } this.kankaku = kankaku; console.log( "点の数", this.tens.length ); //近接するポイントのリストを作成 //ラインのルート作成に必要 let ref = [ [ 0, 1, 0 ], [ 0, -1, 0 ], [ -1, 0, 0 ], [ 1, 0, 0 ], [ 0, 0, -1 ], [ 0, 0, 1 ], ]; for( let j = 0; j < this.points.length; j++ ) { let p = this.points[ j ]; for( let i = 0; i < ref.length; i++ ) { let X = Math.round( ( p.x + ref[ i ][ 0 ] * this.kankaku ) * 10 ) / 10; let Y = Math.round( ( p.y + ref[ i ][ 1 ] * this.kankaku ) * 10 ) / 10; let Z = Math.round( ( p.z + ref[ i ][ 2 ] * this.kankaku ) * 10 ) / 10; let key = X + "," + Y + "," + Z; //check. if( ! tensHash[ key ] ) continue; let p2 = tensHash[ key ][ 3 ]; p.kinsetus.push( p2 ); } } }//bunkatu() }//class Matrix //---objcpy() [ App20200909, Matrix ].map( clss => { clss.prototype.objcpy = function( master ) { let res; if( master instanceof Array ) { res = new Array(); for( let i = 0; i < master.length; i++ ) { res[ i ] = this.objcpy( master[ i ] ); } } else if( master instanceof Object ) { res = new Object(); for( let name in master ) { res[ name ] = this.objcpy( master[ name ] ); } } else { res = master; } return res; } } );