//--Class 1/8 App class App { /* 5W1H がプログラムの説明に使えないか模索: 日常 こんなことがありました、と伝える。(過去伝達) プログラム このように動け、とシステムに伝える。(命令) どんなプログラムなのか、伝えたい。 日常 プログラム いつ どんな時に使う 楽しませたいときに どこで どこで使う ブラウザで。 だれが 誰が使う プログラマー 何を 何を 人物(人形)などを なぜ 動機 滑らかな動きの実現のために どのように どのように 身体をパーツで区切り回転を行い */ /* 追加機能: Poses poseはアニメの「動きのキーポイントの静止画」(原画)にあたる。 「その間の絵をつなげる作業」(中割り)は、前poseと後poseから作られたanmが行う。 posesは原画を集めたもの。 SVCとは別の独立した機能であり、SVCとは切り離して理解することができる。 adjust これがないと、地面に足を立たせる表現ができない。(しづらい) GraphPaper ジャンプの放物線を表現するためのもの。 */ /* メモ: 21/01/23(土) 00:41:43 現在、anm.ready(), anmGroup.ready()についてあやふやな状態。 21/01/17(日) 11:39:24 現在、GraphPaper, Posesあたりが煩雑で、難解な状態。 */ /* なじみのない記述について: ■配列の要素を処理する なじみ深い記述: for( let i = 0; i < array.length; i++ ) { let element = array[ i ]; <処理> } なじみのない記述: array.map( element => { <処理> } ); 利点: 制御用の変数iが不要 制御用の変数iの演算がないので処理が速いかも ■連想配列の値を処理する なじみ深い記述: for( let key in hash ) { let value = hash[ key ]; <処理> } なじみのない記述: Object.values( hash ).map( value => { <処理> } ); 利点: 制御用の変数keyが不要 hashからkeyを検索しないので処理が速いかも ■配列を配列としてではなく、各要素として渡す(スプレッド構文 (...) ) なじみ深い記述: for( let i = 0; i < family.length; i++ ) this.zIndex.push( family[ i ] ); なじみのない記述: this.zIndex.push( ...family ); 利点: 制御用の変数iが不要 制御用の変数iの演算がないので処理が速いかも ECMA6(2015年6月) アロー関数(option => <処理>) Promise ECMA8(2017年6月) 非同期関数 (async/await) Object.values ECMA9(2018年6月) スプレッド構文(...) */ constructor( canvasId ) { console.log( "app constructor" ); window.app = this; this.JSDIR = document.baseURI.replace( /\/[^\/]+\.[^\/]+$/, "" ) + "/20201021-indexJS"; this.timerMs = 100; this.timerCounter = 0; this.cc = document.getElementById( canvasId ).getContext( "2d" ); this.cc.canvas.app = this; this.cc.canvas.onclick = this.reset.bind( this ); this.scale = 1; this.horizon = 0.9; //---images this.images = { atama : new Image(), atama2 : new Image(), atama3 : new Image(), kubi : new Image(), jouwanL : new Image(), zenwanL : new Image(), teL : new Image(), mune : new Image(), kosi : new Image(), momoL : new Image(), suneL : new Image(), kakatoL : new Image(), tumasakiL : new Image(), hara : new Image(), stick : new Image(), test1 : new Image(), test2 : new Image(), } //---canvas臨時拡張メソッド this.cc.tmp = { circle : function( x, y, r, fillStyle, strokeStyle ) { this.cc.beginPath(); this.cc.arc( x, y, r, 0, 6.28, false ); this.cc.closePath(); if( fillStyle ) { this.cc.fillStyle = fillStyle; this.cc.fill(); } if( strokeStyle ) { this.cc.strokeStyle = strokeStyle; this.cc.stroke(); } }.bind( this ), line : function( sx, sy, ex, ey, color ) { this.cc.beginPath(); this.cc.moveTo( sx, sy ); this.cc.lineTo( ex, ey ); if( color ) this.cc.strokeStyle = color; this.cc.stroke(); }.bind( this ), clearAll : function() { this.cc.clearRect( 0, 0, this.cc.tmp.width, this.cc.tmp.height ); //debug. 描画領域を確認したいとき if( 0 ) { this.cc.strokeStyle = "red"; this.cc.strokeRect( - this.cc.canvas.width / this.scale / 2, this.cc.canvas.height * this.horizon / this.scale, this.cc.canvas.width / this.scale, - this.cc.canvas.height / this.scale ); } }.bind( this ), }//this.cc.tmp this.cc.tmp.width = this.cc.canvas.width; this.cc.tmp.height = this.cc.canvas.height; //---リサイズ対応 /* let resizex2 = function( e ) { console.log( "resizex()" ); let canvas = this.cc.canvas; //(canvasの物理領域について) //ウィンドウリサイズに伴うcanvas高さ変更 let H = document.getElementsByTagName( "H1" )[ 0 ]; if( H && canvas.getBoundingClientRect().top - H.getBoundingClientRect().top < 0 ) { canvas.style.height = H.offsetTop + "px"; this.cc.canvas.style.width = "100%"; this.cc.canvas.width = 512; } //※なお、canvas.style.width は100% //(canvasの論理領域について) //canvas解像度変更 let cr = canvas.getBoundingClientRect(); //ウィンドウが縦長にしろ横長にしろ少なくともこの解像度を持たせる let minW = 512; let minH = 448; //minW, minH の矩形は、現在のcanvas物理領域に、 if( cr.width / minW > cr.height / minH ) { console.log( "縦フィットする" ); let pixels = cr.height / minH; //縦の1論理ピクセル当たりの物理ピクセル数 canvas.width = cr.width / pixels; //同じ物理ピクセルで割って解像度を得る。 canvas.height = minH; } else { console.log( "横フィットする" ); let pixels = cr.width / minW; canvas.width = minW; canvas.height = cr.height / pixels; } //---中心位置 let transX = canvas.width / this.scale / 2; let transY = -canvas.height / this.scale * this.horizon; this.cc.scale( this.scale, this.scale ); this.cc.tmp.genten_Y = 200; this.cc.tmp.genten_X = canvas.width / this.scale / 2; if( e ) this.draw( this.cc ); }.bind( this ); //resizex2() */ this.zIndex = new Array(); //順序に意味がある this.svcs = new Array(); //ディレクトリ構造 親から子へcalc結果を伝えるのに this.anms = new Array(); this.drawings = new Array(); this.poses = new Poses(); this.drawings.push( this.zIndex ); //---テスト実行 動かすための必要最低限がこのifブロック内に書かれている。 if( 0 ) { let test1 = new SVC_Part( "test1" ); ( function( svc ) { //with svc.addJoint( "ジョイント1", 70, 50 ); svc.image = this.images[ svc.name ]; svc.imageZoom = 0.25; svc.initialX = -20; svc.initialY = 0; svc.initialRotation = 0.1; svc.debug.joint = true; svc.debug.jointname = true; svc.debug.genten = true; } ).call( this, test1 ); let test2 = new SVC_Part( "test2" ); ( function( svc ) { //with svc.addJoint( "ジョイント2", 0, 50 ); svc.addJoint( "ジョイント2-2", 0, 25 ); svc.image = this.images[ svc.name ]; svc.imageZoom = 0.25; svc.debug.joint = false; svc.debug.jointname = false; svc.debug.opacity = .5; svc.debug.genten = false; } ).call( this, test2 ); //1:親子関係にする, 0:友人関係にする if( 1 ) test1.connect( "ジョイント1", test2, "ジョイント2-2" ); else this.svcs.push( test2 ); this.svcs.push( test1 ); this.zIndex.push( test1, test2 ); this.poses.add( "test", new Pose( 3000, this.timerMs, { list : [ [ test1, "initialRotation", 3.14/2 ], [ test2, "initialRotation", 3.14 ], ], } ), ); this.poses.add( "test2", new Pose( 3000, this.timerMs, { list : [ [ test1, "initialRotation", 0 ], [ test2, "initialRotation", 0 ], ], } ), ); //1回目の描画 // resizex2(); this.anms.push( this.poses.exportToAnms() ); return; }//テスト実行 //---SVC_Part定義 - world let world = new SVC_Part( "world" ); ( function( svc ) { //with svc.addJoint( "世界>キャラ1", -100, 0 ); svc.drawImage = function( cc ) { cc.fillStyle = "brown"; let w = this.cc.tmp.width / 2; let h = this.cc.tmp.height * 0.11; cc.fillRect( -w, 0, w * 2, h ); }.bind( this ); } ).call( this, world ); let imageZoomMaster = 0.263; let zz = 1.65; //---SVC_Part定義 - hara let hara = new SVC_Part( "hara" ); ( function( svc ) { //with svc.initialY = 100 * zz; svc.imageX = -12.5 * zz; svc.imageY = -15 * zz; svc.addJoint( "腹>胸", 0, 0 ); svc.addJoint( "腹>腰", 0, -15 * zz ); svc.imageZoom = imageZoomMaster * zz; } ).call( this, hara ); //---SVC_Part定義 - mune let mune = new SVC_Part( "mune" ); ( function( svc ) { //with() {} svc.imageX = -12.75 * zz; svc.imageY = -5 * zz; svc.addJoint( "胸>首", 1 * zz, 17 * zz ); svc.addJoint( "胸>左上腕", 3 * zz, 16 * zz ); svc.imageZoom = imageZoomMaster * zz; } ).call( this, mune ); //---SVC_Part定義 - jouwanL let jouwanL = new SVC_Part( "jouwanL" ); ( function( svc ) { //with let add = -1 * zz; svc.imageX = -6 * zz + add; svc.imageY = -26 * zz; svc.addJoint( "左上腕>左前腕", 2 * zz + add, -20 * zz - 3 * zz ); svc.imageZoom = imageZoomMaster * zz; // svc.debug.joint = true; } ).call( this, jouwanL ); //---SVC_Part定義 - zenwanL let zenwanL = new SVC_Part( "zenwanL" ); ( function( svc ) { //with let add = 2 * zz; svc.imageX = -7 * zz; svc.imageY = -21 * zz - add; svc.addJoint( "左前腕>左手", -2 * zz, -20 * zz - add ); svc.imageZoom = imageZoomMaster * zz; } ).call( this, zenwanL ); //---SVC_Part定義 - teL let teL = new SVC_Part( "teL" ); ( function( svc ) { //with svc.imageX = -5 * zz; svc.imageY = -10 * zz; svc.addJoint( "つかみ", 0, -5 * zz ); svc.imageZoom = imageZoomMaster * zz; } ).call( this, teL ); //---SVC_Part定義 - kubi let kubi = new SVC_Part( "kubi" ); ( function( svc ) { //with svc.imageX = -6 * zz; svc.imageY = -2 * zz; svc.addJoint( "首>頭", 0, 6 * zz ); svc.imageZoom = imageZoomMaster * zz; } ).call( this, kubi ); //---SVC_Part定義 - atama let atama = new SVC_Part( "atama" ); ( function( svc ) { //with svc.image = this.images.svc; svc.imageX = -18 * zz; svc.imageY = -2 * zz; svc.imageZoom = imageZoomMaster * zz; } ).call( this, atama ); //---SVC_Part定義 - kosi let kosi = new SVC_Part( "kosi" ); ( function( svc ) { //with svc.imageX = -13.5 * zz; svc.imageY = -16 * zz; svc.addJoint( "腰>左もも", -2.5 * zz, -10 * zz ); svc.imageZoom = imageZoomMaster * zz; } ).call( this, kosi ); //---SVC_Part定義 - momoL let momoL = new SVC_Part( "momoL" ); ( function( svc ) { //with let add = 3 * zz; svc.imageX = -10 * zz; svc.imageY = -28 * zz - add - 2 * zz; svc.addJoint( "左もも>左すね", -2.5 * zz, -27.5 * zz - add ); svc.imageZoom = imageZoomMaster * zz; //svc.debug.opacity = 1; } ).call( this, momoL ); //---SVC_Part定義 - suneL let suneL = new SVC_Part( "suneL" ); ( function( svc ) { //with let add = 7 * zz; svc.imageX = -6.5 * zz; svc.imageY = -28 * zz - add + 2 * zz; svc.addJoint( "左すね>左かかと", 2.5 * zz, -25 * zz - add ); svc.imageZoom = imageZoomMaster * zz; } ).call( this, suneL ); //---SVC_Part定義 - kakatoL let kakatoL = new SVC_Part( "kakatoL" ); ( function( svc ) { //with svc.imageX = -12 * zz; svc.imageY = -6 * zz; svc.addJoint( "左かかと>左つま先", -10 * zz, -2 * zz ); svc.addJoint( "あしのうら", -10 * zz, -5 * zz ); svc.imageZoom = imageZoomMaster * zz; } ).call( this, kakatoL ); //---SVC_Part定義 - tumasakiL let tumasakiL = new SVC_Part( "tumasakiL" ); ( function( svc ) { //with svc.imageX = -10 * zz; svc.imageY = -4 * zz; svc.addJoint( "あしのうら", -10 * zz, 0 ); svc.imageZoom = imageZoomMaster * zz; } ).call( this, tumasakiL ); //---SVC_Part定義 - stick let stick = new SVC_Part( "stick" ); ( function( svc ) { //with svc.imageX = 0; svc.imageY = 0; svc.addJoint( "真ん中", 45 * zz, 40 * zz ); svc.imageZoom = 0.28 * zz; } ).call( this, stick ); //---パーツどうしの接続 teL.connect( "つかみ", stick, "真ん中" ); zenwanL.connect( "左前腕>左手", teL ); jouwanL.connect( "左上腕>左前腕", zenwanL ); mune.connect( "胸>左上腕", jouwanL ); kubi.connect( "首>頭", atama ); mune.connect( "胸>首", kubi ); hara.connect( "腹>胸", mune ); hara.connect( "腹>腰", kosi ); kosi.connect( "腰>左もも", momoL ); momoL.connect( "左もも>左すね", suneL ); suneL.connect( "左すね>左かかと", kakatoL ); kakatoL.connect( "左かかと>左つま先", tumasakiL ); //---パーツどうしの重ね前後関係 atama.z = 0; kubi.z = -1; mune.z = 0; jouwanL.z = 3; zenwanL.z = 3; teL.z = 3; hara.z = 0; kosi.z = 0; momoL.z = 0; suneL.z = 0; kakatoL.z = 0; tumasakiL.z = 0; stick.z = 2; //---地面に立たせる window.tumasakiL = tumasakiL; window.atama = atama; hara.adjust( tumasakiL, "あしのうら", { x : { object : world.joints[ "世界>キャラ1" ], propertyName : "absoluteX", }, y : { object : world.joints[ "世界>キャラ1" ], propertyName : "absoluteY", }, rotation : { object : world, propertyName : "absoluteRotation", }, } ); hara.initialX = -100; /* 日本語訳: (haraをトップとして各パーツは相対的に位置が計算されるので) haraについて補正値を求める。( tumasakiL の、 ジョイント名 "あしのうら" を(地面に)接触させたい。{ "あしのうら" の x 座標について world(地面)のジョイント"世界>キャラ1"の、 プロパティ"absoluteX"を 接触先とし、その値と "あしのうら" の x 座標(内部的にabsoluteX)の値の 差を補正値とする。 ("あしのうら"が地面に接触するためにその補正値だけ移動するなら、 haraの移動量もその補正値だけ移動すればよい。 なぜならharaと"あしのうら"の位置関係は変わらないから) "あしのうら" の y 座標について (xと同様) "あしのうら" の rotation について worldの傾き("absoluteRotation")に合わせる。 }); haraは画面の原点(中心)から100だけ左側によったところにある。 */ //---svcs.push this.svcs.push( world ); this.svcs.push( hara ); //---zindex //svcs内の各SVCトップが持つ子孫をすべて zIndex へおさめる。 this.svcs.map( function( svc ) { let family = svc.getFamily(); console.log( svc.name, family.length ); this.zIndex.push( ...family ); }.bind( this ) ); //それをソート this.zIndex.sort( function( a, b ) { if( a.z < b.z ) return -1; else if( a.z > b.z ) return 1; else return 0; } ); //---imageを適用 for( let i = 0; i < this.zIndex.length; i++ ) { let svc = this.zIndex[ i ]; //check. drawImage()を書き換えている場合はimage(画像)不要とみなす。 if( svc.drawImage != SVC_Part.prototype.drawImage ) { console.log( svc.name, "は drawImage() を書き換えているので、image(画像)は不要とみなしました." ); continue; } //check. 対応する画像がない if( ! this.images[ svc.name ] ) alert( svc.name + " の画像 " + svc.name + ".png がない" ); //image(画像)適用 svc.image = this.images[ svc.name ]; } //---poses /* posesのイメージ poses = { pose1 : [ [オブジェクト, プロパティ名, 目的の値] [オブジェクト, プロパティ名, 目的の値] .. ], pose2 : [ [オブジェクト, プロパティ名, 目的の値] [オブジェクト, プロパティ名, 目的の値] .. ], .. } poses.toAnms()により、pose1とpose2は連結される */ let tick = 3.14 / 10; this.poses.add( "chokuritu", new Pose( 1000, this.timerMs, { list : [ //数値: 減らす時計回り 増やす反時計まわり [ hara, "initialRotation", 0 ], [ atama, "initialRotation", tick * 0 ], [ kubi, "initialRotation", tick * 0 ], [ mune, "initialRotation", tick * 0 ], [ kosi, "initialRotation", tick * 0 ], [ momoL, "initialRotation", tick * 0 ], [ suneL, "initialRotation", tick * 0 ], [ kakatoL, "initialRotation", tick * 0 ], [ tumasakiL, "initialRotation", tick * 0 ], [ jouwanL, "initialRotation", tick * 0 ], [ zenwanL, "initialRotation", tick * 0 ], [ teL, "initialRotation", tick * 0 ], ], } ) ); this.poses.add( "kagami", new Pose( 500, this.timerMs, { list : [ //数値: 減らす時計回り 増やす反時計まわり [ hara, "initialRotation", 0 ], [ atama, "initialRotation", tick * -.5 ], [ kubi, "initialRotation", tick * 0 ], [ mune, "initialRotation", tick * .5 ], [ kosi, "initialRotation", tick * -1 ], [ momoL, "initialRotation", tick * -1.5 ], [ suneL, "initialRotation", tick * 2.5 ], [ kakatoL, "initialRotation", tick * -1.5 ], [ tumasakiL, "initialRotation", tick * 0 ], [ jouwanL, "initialRotation", tick * -4 ], [ zenwanL, "initialRotation", tick * -2 ], [ teL, "initialRotation", tick * -1 ], ], } ) ); this.poses.add( "senobi", new Pose( 300, this.timerMs, { list : [ //数値: 減らす時計回り 増やす反時計まわり [ atama, "initialRotation", tick * -1.5 ], [ kubi, "initialRotation", tick * -1 ], [ mune, "initialRotation", tick * -0.5 ], [ kosi, "initialRotation", tick * 0.5 ], [ momoL, "initialRotation", tick * 0.5 ], [ suneL, "initialRotation", tick * 0 ], [ kakatoL, "initialRotation", tick * 2.5 ], [ tumasakiL, "initialRotation", tick * -3 ], [ jouwanL, "initialRotation", tick * -12 ], [ zenwanL, "initialRotation", tick * 0 ], [ teL, "initialRotation", tick * -1 ], ], } ) ); //---graphPaper //posesの定義で参照するので、ここで定義する必要がある。 this.graphPaper = new GraphPaper( "方眼紙1" ); this.svcs.unshift( this.graphPaper ); this.zIndex.push( this.graphPaper ); this.poses.add( "bridge", new Pose( 1000, this.timerMs, { start : function() { //放物線の始点と終点、頂点の高さを決め打ちする let sx = 0; //始点 let sy = 0; let ex = this.baktenEndX; //終点 let ey = -15 * zz; //バク転の手をつく高さなどになる let cy = Math.max( sy, ey ) + this.baktenTakasa; //頂点y座標 let equ = this.mathGetABCByPoints( sx, sy, ex, ey, cy ); //※方程式は英語で equation その略で equ //y = ax^2 + bx + c の a, b, c を equ に収めている。 this.graphPaper.setting( { f : function( x ) { return equ.a * x * x + equ.b * x + equ.c; }, sx : sx, ex : ex, zoomX : 1, zoomY : 1, zoom : 1, visibility : false, //表示用 sy : sy, ey : ey, cy : cy, //放物線の頂点までは、このアニメである。下部 ※1 cx : - equ.b / ( 2 * equ.a ), //頂点x座標 initialX : hara.genten.absoluteX, initialY : hara.genten.absoluteY, } ); hara.initialRotation = -tick * .5; hara.adjust( hara, "腹>腰", { x : { object : this.graphPaper.jointP, propertyName : "absoluteX", }, y : { object : this.graphPaper.jointP, propertyName : "absoluteY", }, } ); }, list : [ //数値: 減らす時計回り 増やす反時計まわり [ this.graphPaper, "px", "cx" ], //※1 [ hara, "initialRotation", -3.14 / 2 - .1 ], [ atama, "initialRotation", tick * -1 ], [ kubi, "initialRotation", tick * -2 ], [ mune, "initialRotation", -3.14 /10 ], [ kosi, "initialRotation", 3.14 /10 ], [ momoL, "initialRotation", 3.14 /6 ], [ suneL, "initialRotation", 3.14 /4 ], [ kakatoL, "initialRotation", 3.14 /5 ], [ tumasakiL, "initialRotation", 3.14 /4 ], [ jouwanL, "initialRotation", tick * -10 ], [ zenwanL, "initialRotation", tick * 0 ], [ teL, "initialRotation", tick * 0 ], ], } ) ); this.poses.add( "sakadachi", new Pose( 1000, this.timerMs, { start : function() { atama.image = this.images.atama2; }, list : [ //数値: 減らす時計回り 増やす反時計まわり [ this.graphPaper, "px", "ex" ], [ hara, "initialRotation", -3.05 ], [ atama, "initialRotation", tick * -1 ], [ kubi, "initialRotation", tick * -2 ], [ mune, "initialRotation", tick * -0.5 ], [ kosi, "initialRotation", tick * 0.5 ], [ momoL, "initialRotation", tick * 0 ], [ suneL, "initialRotation", tick * 0 ], [ kakatoL, "initialRotation", 3.14 /4 ], [ tumasakiL, "initialRotation", tick * 1 ], [ jouwanL, "initialRotation", tick * -10], [ zenwanL, "initialRotation", tick * 0 ], [ teL, "initialRotation", tick * -0.5 ], ], } ) ); this.poses.add( "sakadachi2", new Pose( 200, this.timerMs, { start : function() { hara.adjust( teL, "つかみ", { x : teL.genten.absoluteX, y : teL.genten.absoluteY - 5 * zz, } ); // this.graphPaper.visibility = false; }, list : [ //数値: 減らす時計回り 増やす反時計まわり [ hara, "initialRotation", -3.14 - 0.1 ], [ atama, "initialRotation", tick * -1 ], [ kubi, "initialRotation", tick * -2 ], [ mune, "initialRotation", tick * -0.5 ], [ kosi, "initialRotation", tick * 0 ], [ momoL, "initialRotation", tick * 0 ], [ suneL, "initialRotation", tick * 0 ], [ kakatoL, "initialRotation", 3.14 /4 ], [ tumasakiL, "initialRotation", tick * 1 ], [ jouwanL, "initialRotation", tick * -10], [ zenwanL, "initialRotation", tick * 0 ], [ teL, "initialRotation", tick * -0.5 ], ], } ) ); this.poses.add( "a_10en_ochiteru", new Pose( 1000, this.timerMs, { start : function() { hara.releaseAdjust(); hara.initialX = hara.genten.absoluteX - 3 * zz; hara.initialY = hara.genten.absoluteY + 10 * zz; }, list : [ //数値: 減らす時計回り 増やす反時計まわり [ hara, "initialRotation", -5.25 ], [ atama, "initialRotation", tick * .5 ], [ kubi, "initialRotation", tick * 0 ], [ mune, "initialRotation", tick * 1 ], [ kosi, "initialRotation", tick * -1.5 ], [ momoL, "initialRotation", tick * -2.5 ], [ suneL, "initialRotation", tick * 0 ], [ kakatoL, "initialRotation", tick * 0 ], [ tumasakiL, "initialRotation", tick * 0 ], [ jouwanL, "initialRotation", tick * -5 ], [ zenwanL, "initialRotation", tick * 0 ], [ teL, "initialRotation", tick * -1 ], ], } ) ); this.poses.add( "ojigi", new Pose( 250, this.timerMs, { start : function() { hara.adjust( tumasakiL, "あしのうら", { x : tumasakiL.genten.absoluteX - 5 * zz, y : tumasakiL.genten.absoluteY + 3 * zz, } ); }, list : [ //数値: 減らす時計回り 増やす反時計まわり [ hara, "initialRotation", -5.25 ], [ atama, "initialRotation", tick * -.5 ], [ kubi, "initialRotation", tick * 0 ], [ mune, "initialRotation", tick * 1 ], [ kosi, "initialRotation", tick * -1.5 ], [ momoL, "initialRotation", tick * -3 ], // [ suneL, "initialRotation", tick * 0.5 ], // [ kakatoL, "initialRotation", tick * -0.5 ], // [ tumasakiL, "initialRotation", tick * 0 ], [ jouwanL, "initialRotation", tick * -5 ], [ zenwanL, "initialRotation", tick * 0 ], [ teL, "initialRotation", tick * -1 ], ], } ) ); //---graphPaper2 //posesの定義で参照するので、ここで定義する必要がある。 this.graphPaper2 = new GraphPaper( "方眼紙2" ); this.svcs.unshift( this.graphPaper2 ); this.zIndex.push( this.graphPaper2 ); this.poses.add( "waai", new Pose( 1000, this.timerMs, { start : function() { //放物線の始点と終点、頂点の高さを決め打ちしない //(実際に動かして目で見て微調整するやりかた) this.graphPaper2.setting( { //数学関数 f : function( x ) { return -2 * x * x; }, sx : -10, //点p の x 座標の初期値となる ex : 9.8, //点p の x 座標の終了値となる //(上記関数の引数 x は sx から始まり ex まで進む) zoomX : 1.6, //方眼紙の x 方向拡大率 zoomY : 0.4 * zz, //方眼紙の y 方向拡大率 zoom : 2, //方眼紙の全体の拡大率 visibility : false, //方眼紙の画面上の位置 initialX : hara.genten.absoluteX + 5, initialY : hara.genten.absoluteY - 5, } ); hara.adjust( hara, "腹>腰", { x : { object : this.graphPaper2.jointP, propertyName : "absoluteX", }, y : { object : this.graphPaper2.jointP, propertyName : "absoluteY", }, } ); console.log( "px", this.graphPaper2.px ); }, list : [ //数値: 減らす時計回り 増やす反時計まわり [ this.graphPaper2, "px", -7.75 ], [ atama, "initialRotation", 0], [ kubi, "initialRotation", 0 ], [ mune, "initialRotation", -0.12 ], [ hara, "initialRotation", -6.45 ], [ kosi, "initialRotation", 0.12 ], [ momoL, "initialRotation", 0 ], [ suneL, "initialRotation", 0 ], [ kakatoL, "initialRotation", tick * 4 ], [ tumasakiL, "initialRotation", tick * 1 ], [ jouwanL, "initialRotation", tick * -10 ], [ zenwanL, "initialRotation", tick * -.5 ], [ teL, "initialRotation", tick * -1 ], ], } ) ); this.poses.add( "sinsin", new Pose( 1000, this.timerMs, { list : [ //数値: 減らす時計回り 増やす反時計まわり [ this.graphPaper2, "px", -1 ], [ hara, "initialRotation", -10 ], [ atama, "initialRotation", tick * -.5 ], [ kubi, "initialRotation", tick * -.5 ], [ mune, "initialRotation", tick * -1 ], [ kosi, "initialRotation", tick * .8 ], [ momoL, "initialRotation", tick * 1.5 ], [ suneL, "initialRotation", tick * 1 ], [ kakatoL, "initialRotation", tick * 4 ], [ tumasakiL, "initialRotation", tick * 1 ], [ jouwanL, "initialRotation", tick * 1.5 ], [ zenwanL, "initialRotation", tick * 0 ], [ teL, "initialRotation", tick * 0 ], ], } ) ); this.poses.add( "kagami3", new Pose( 1000, this.timerMs, { list : [ //数値: 減らす時計回り 増やす反時計まわり [ this.graphPaper2, "px", "ex" ], [ hara, "initialRotation", -11.45 ], [ atama, "initialRotation", tick * 0 ], [ kubi, "initialRotation", tick * 0 ], [ mune, "initialRotation", tick * 1 ], [ kosi, "initialRotation", tick * -1.5 ], [ momoL, "initialRotation", tick * -2.5 ], [ suneL, "initialRotation", tick * 0 ], [ kakatoL, "initialRotation", tick * 0 ], [ tumasakiL, "initialRotation", tick * 0 ], [ jouwanL, "initialRotation", tick * -2 ], [ zenwanL, "initialRotation", tick * 0 ], [ teL, "initialRotation", tick * -1 ], ], end : async function() { await this.delay( 400 ); atama.image = this.images.atama; atama.draw( this.cc ); await this.delay( 450 ); }, } ) ); this.poses.add( "chokuritu2", new Pose( 1000, this.timerMs, { start : function() { hara.adjust( tumasakiL, "あしのうら", { x : tumasakiL.genten.absoluteX - 10 * zz, y : tumasakiL.genten.absoluteY + 1.5 * zz, } ); // this.graphPaper2.visibility = false; tumasakiL.rotation = 0; }, list : [ //数値: 減らす時計回り 増やす反時計まわり [ atama, "initialRotation", tick * -.25 ], [ kubi, "initialRotation", tick * -.25 ], [ hara, "initialRotation", -3.15 * 4 ], [ mune, "initialRotation", tick * -.25 ], [ kosi, "initialRotation", tick * .25 ], [ momoL, "initialRotation", tick * .25 ], [ suneL, "initialRotation", 0 ], [ kakatoL, "initialRotation", -.12 ], [ tumasakiL, "initialRotation", 0 ], [ jouwanL, "initialRotation", tick * -10.5 ], [ zenwanL, "initialRotation", tick * -1 ], ], end : function() { atama.image = this.images.atama3; }, } ) ); //---/poses //---実行 this.anms.push( this.poses.exportToAnms() ); //バク転の大きさを乱数で決めている。 this.baktenEndX = 50 + Math.random() * 80; this.baktenTakasa = 20 + Math.random() * 80; //1回目の描画 // resizex2(); // addEventListener( "resize", resizex2 ); //以降、start()メソッドの実行で動作開始 }//App constructor() //-- Method reset (App 1/10) //全体を最初からやり直す reset( e ) { this.stop(); let hara, world; this.svcs.map( svc => { switch( svc.name ) { case "hara": hara = svc; break; case "world": world = svc; break; } } ); window.atama.image = this.images.atama; hara.adjust( window.tumasakiL, "あしのうら", { x : { object : world.joints[ "世界>キャラ1" ], propertyName : "absoluteX", }, y : { object : world.joints[ "世界>キャラ1" ], propertyName : "absoluteY", }, rotation : { object : world, propertyName : "absoluteRotation", }, } ); hara.initialX = -100; hara.initialRotation = 0; this.anms.length = 0; this.anms.push( this.poses.exportToAnms() ); //バク転の大きさを乱数で決めている。 this.baktenEndX = 50 + Math.random() * 80; this.baktenTakasa = 20 + Math.random() * 80; this.start(); } _reset( e ) { this.stop(); if( window.apps ) { let idx = window.apps.indexOf( this ); window.apps.splice( idx, 1 ); window.app = new App( this.cc.canvas.id ); window.app.start(); window.apps.push( window.app ); } else { window.app = new App( this.cc.canvas.id ); window.app.start(); } } //-- Method delay (App 2/10) delay( ms ) { return new Promise( function( tellOk ) { setTimeout( tellOk, ms ); } ); } //-- Method frame (App 3/10) async frame() { for( let i = 0; i < this.anms.length; i++ ) { let anm = this.anms[ i ]; //check. アニメ開始時 if( ! anm.startFlg ) { console.log( " B start1", anm.name, anm.class ); anm.startFlg = true; await anm.start.call( this ); anm.ready(); } anm.frame.call( anm ); //check. アニメ終了時 if( anm.endFlg ) { console.log( " B end", anm.name ); this.anms.splice( i, 1 ); i--; await anm.end.call( this ); //アニメの事後関数実行 //次に続くアニメを用意 if( anm.next ) this.anms.push( anm.getNext() ); } }//for anms this.calc(); this.draw( this.cc ); this.timerCounter ++; }//frame() //-- Method calc (App 4/10) calc() { this.svcs.map( svc => { svc.calc1(); svc.calc2(); } ); } //-- Method start (App 5/10) async start() { console.log( "app start" ); //画像プリロード if( typeof this.loading === "undefined" ) { console.log( "preload開始" ); this.loading = "loading"; this.draw( this.cc ); await this.preload(); } this.loading = "ok"; console.log( "プログラム開始" ); //アニメの実行形態 switch( "" ) { case "key": //キー入力で進める onkeydown = this.frame.bind( this ); break; case "one": //1回描画して終了 this.draw( this.cc ); break; case "slow": //ゆっくりアニメ if( typeof this.timerId === "undefined" ) this.timerId = setInterval( this.frame.bind( this ), 500 ); break; case "normal": //通常アニメ default: if( typeof this.timerId === "undefined" ) this.timerId = setInterval( this.frame.bind( this ), this.timerMs ); break; } } //-- Method stop (App 6/10) stop() { console.log( "app stop" ); clearInterval( this.timerId ); delete this.timerId; } //-- Method draw (App 7/10) draw( cc ) { this.cc.tmp.genten_Y = cc.tmp.height * .89; this.cc.tmp.genten_X = cc.tmp.width / 2; this.cc.tmp.clearAll(); // cc.strokeRect( 0, 0, cc.tmp.width, cc.tmp.height ); //check. 画像プリロード中 if( this.loading == "loading" ) { cc.fillStyle = "green"; cc.fillText( "LOADING...", 16, 16 ); return; } //描画 this.drawArray( this.drawings ); //画面の中心 if( 0 ) { cc.tmp.circle( 0, 0, 8, "red", "red" ); } //仮想原点 if( 0 ) { cc.tmp.line( 0, cc.tmp.genten_Y, this.cc.tmp.width, cc.tmp.genten_Y, "gray" ); cc.tmp.line( cc.tmp.genten_X, 0, cc.tmp.genten_X, this.cc.tmp.height, "gray" ); } cc.fillText( this.timerCounter, 0, 10 ); } //-- Method drawArray (App 8/10) drawArray( array ) { //渡された配列について、各要素のdrawメソッドを呼ぶ for( let i = 0; i < array.length; i++ ) { let drawing = array[ i ]; //check. 配列の要素がさらに配列ならば if( Array.isArray( drawing ) ) { this.drawArray( drawing ); continue; } //check. 要素が非表示指定ならば if( ! drawing.visibility ) continue; drawing.draw( this.cc ); } } //-- Method preload (App 9/10) preload() { console.log( location.href ); let dir = this.JSDIR; return new Promise( function( youSayMissionComplete ) { let cnt = 0; let max = Object.keys( this.images ).length; //各画像について for( let name in this.images ) { //画像のonload定義 this.images[ name ].onload = function( e ) { cnt++; console.log( name, "loaded. (" + cnt + " of " + max + ")" ); //check. すべて読み込んだとき if( cnt == max ) { youSayMissionComplete(); } }.bind( this ); console.log( name, "loading.." ); //画像のsrc定義 this.images[ name ].src = dir + "/_svcimg/" + name + ".png"; } }.bind( this ) ); }//preload() //-- Method mathGetABCByPoints (App 10/10) mathGetABCByPoints( sx, sy, ex, ey, cy ) { //始点と終点、頂点y座標から、放物線方程式の係数 a, b, c を求める let sx_2 = sx * sx; let ex_2 = ex * ex; let r = sx - ex; let u = sx_2 - ex_2; let t = sy - ey; let r4 = r * 4; let A = r4 * ex * ( -ex * r + sx_2 - ex_2 ) - u * u; let B = r4 * ( ey * r - ex * t - cy * r ) + 2 * t * u; let C = - t * t; let a = ( -B + Math.sqrt( B * B - 4 * A * C ) ) / ( 2 * A ); let b = ( t - a * u ) / r; let c = ey - a * ex_2 - b * ex; return { a : a, b : b, c : c, } }//getABCByPoints() }//class App //--Class 2/8 SVC_Part class SVC_Part { constructor( name ) { this.name = name; this.image = null; this.imageX = 0; this.imageY = 0; this.imageZoom = 1; this.visibility = true; //親を持たない場合に使用される変数 this.initialX = 0; //this.gentenからの相対 this.initialY = 0; //this.gentenからの相対 this.initialRotation = 0; //this.selfJointを中心とした回転 this.joints = new Object(); this.genten = this.addJoint( "原点", 0, 0 ); this.parentJoint = null; //接続先の親ジョイント this.selfJoint = this.genten; //接続元の自ジョイント this.parent = null; this.children = new Array(); this.adjustArgs = null; this.z = 0; this.debug = { name : false, genten : false, joint : false, jointname : false, jointnameY : 0, opacity : 1, //1は不透明 } }//SVC_Part constructor() //-- Method getFamily (SVC_Part 1/12) getFamily( family ) { //check. 最初に呼ばれた場合、配列を作成 if( ! family ) family = [ this ]; this.children.map( function( child ) { family.push( child ); family.push( ...child.getFamily( new Array() ) ); } ); return family; } //-- Method addJoint (SVC_Part 2/12) addJoint( name, initialX, initialY ) { this.joints[ name ] = new Joint( name, initialX, initialY ); return this.joints[ name ]; } //-- Method connect (SVC_Part 3/12) connect( jointName, child, childJointName ) { //check. 自分自身を子にしようとした if( this == child ) { alert( this.name + "は、connect()において、自分自身を子にしようとした." ); return; } //check. 省略時は原点 if( childJointName == null ) { childJointName = "原点"; } //check. そのジョイント名は存在しない。 if( ! this.joints[ jointName ] ) { let mes = "The joint name does not exist."; mes += "joint name: " + this.name + "." + jointName; mes += "at connect()"; alert( mes ); } //check. そのジョイント名は存在しない。 if( ! child.joints[ childJointName ] ) { let mes = "The joint name does not exist."; mes += "joint name: " + child.name + "." + childJointName; mes += "at connect()"; alert( mes ); } this.children.push( child ); child.parent = this; child.parentJoint = this.joints[ jointName ]; child.selfJoint = child.joints[ childJointName ]; }//connect() //-- Method adjust (SVC_Part 4/12) adjust( fromSVC, fromJointName, toInfo ) { //toInfoはx,y,rotationの3つの情報を内包する。 //一部の情報をnullにしてもよい。たとえば、 //xを指定し、yを指定しない場合: 高さはそのままで、横方向のみfromSVCに追従する //rotationだけを指定する場合: 回転のみfromSVCに追従する。 this.adjustArgs = { fromSVC : fromSVC, fromJoint : fromSVC.joints[ fromJointName ], toInfo : toInfo, } } //-- Method releaseAdjust (SVC_Part 5/12) releaseAdjust() { this.adjustArgs = null; } //-- Method calc1 (SVC_Part 6/12) calc1() { /* 絶対座標を計算する。*/ //絶対座標を計算する。原点 if( this.parent ) { //親がある場合 this.absoluteRotation = this.parent.absoluteRotation + this.initialRotation; let res = this.mathRotate( -this.selfJoint.initialX, -this.selfJoint.initialY, this.absoluteRotation ); this.genten.absoluteX = this.parentJoint.absoluteX + res.X; this.genten.absoluteY = this.parentJoint.absoluteY + res.Y; } else { //親がない場合 this.absoluteRotation = this.initialRotation; let res = this.mathRotate( this.initialX, this.initialY, this.absoluteRotation ); this.genten.absoluteX = this.initialX; this.genten.absoluteY = this.initialY; } //絶対座標を計算する。各ジョイント Object.values( this.joints ).map( joint => { //check. if( joint == this.genten ) return; let res = this.mathRotate( joint.initialX, joint.initialY, this.absoluteRotation ); joint.absoluteX = this.genten.absoluteX + res.X; joint.absoluteY = this.genten.absoluteY + res.Y; } ); //絶対座標を計算する。子 this.children.map( child => child.calc1() ); }//calc1() //-- Method calc2 (SVC_Part 7/12) calc2() { //check. adjustの指定がないなら、ディレクトリ構造の下を見ていく if( ! this.adjustArgs ) { this.children.map( child => child.calc2() ); return; } /* adjustの値を計算する (adjustX, adjustY, cx, cy, adjustRotation) */ let fromSVC = this.adjustArgs.fromSVC; let fromJoint = this.adjustArgs.fromJoint; let toInfo = this.adjustArgs.toInfo; //x, y, r 各値の移動量(adjustにより補正される量)を計算する //つまり、adjustしていない宙に浮いた状態から、 //adjustしている(地面に足を付けている)状態の //2つの状態の座標の差を計算する。 let adjustX, cx; if( typeof toInfo.x !== "undefined" ) { //x座標についてadjustする let toX; if( typeof toInfo.x === "object" ) toX = toInfo.x.object[ toInfo.x.propertyName ]; else toX = toInfo.x; adjustX = toX - fromJoint.absoluteX; cx = toX; } else { //x座標についてはadjustしない(y座標だけadjustする) adjustX = 0; cx = this.parentJoint ? this.parentJoint.absoluteX : this.genten.absoluteX; } let adjustY, cy; if( typeof toInfo.y !== "undefined" ) { //y座標についてadjustする let toY; if( typeof toInfo.y === "object" ) toY = toInfo.y.object[ toInfo.y.propertyName ]; else toY = toInfo.y; adjustY = toY - fromJoint.absoluteY; cy = toY; } else { //y座標についてはadjustしない(x座標だけadjustする) adjustY = 0; cy = this.parentJoint ? this.parentJoint.absoluteY : this.genten.absoluteY; } let adjustRotation; if( typeof toInfo.rotation !== "undefined" ) { //回転についてadjustする let toR; if( typeof toInfo.rotation === "object" ) toR = toInfo.rotation.object[ toInfo.rotation.propertyName ]; else toR = toInfo.rotation; //※このtoRはtoSVCを0度としたときの、fromSVCの角度 adjustRotation = toR - fromSVC.absoluteRotation; } else { adjustRotation = 0; } this.calc3( adjustX, adjustY, cx, cy, adjustRotation ); }//calc2() //-- Method calc3 (SVC_Part 8/12) calc3( adjustX, adjustY, cx, cy, adjustRotation ) { //絶対座標にadjustを加える this.absoluteRotation += adjustRotation; //各jointも座標をadjust回転 Object.values( this.joints ).map( joint => { //仮の絶対座標にadjustを加える joint.absoluteX += adjustX; joint.absoluteY += adjustY; //※jointにはrotaionはない //座標をadjust回転 let res = this.mathRotateC( cx, cy, joint.absoluteX, joint.absoluteY, adjustRotation ); joint.absoluteX = res.X; joint.absoluteY = res.Y; } ); //子 this.children.map( child => child.calc3( adjustX, adjustY, cx, cy, adjustRotation ) ); }//calc3() //-- Method drawImage (SVC_Part 9/12) drawImage( cc ) { cc.save(); cc.globalAlpha = this.debug.opacity; cc.drawImage( this.image, this.imageX, -this.imageY, this.image.width * this.imageZoom, -this.image.height * this.imageZoom ); cc.restore(); } //-- Method draw (SVC_Part 10/12) draw( cc ) { //自身を描画 cc.save(); // cc.translate( cc.tmp.genten_X, cc.tmp.genten_Y ); cc.save(); //計算した絶対座標へ移動、回転 cc.translate( this.genten.absoluteX, -this.genten.absoluteY ); cc.rotate( -this.absoluteRotation ); //画像を描画 this.drawImage( cc ); if( this.debug.name ) { cc.fillStyle = "green"; cc.fillText( this.name, 16, -4 ); } cc.restore(); //debug. if( this.debug.joint || this.debug.genten ) { //各jointを○で示す for( let name in this.joints ) { let joint = this.joints[ name ]; cc.save(); cc.translate( joint.absoluteX, -joint.absoluteY ); //原点 if( this.debug.genten && joint == this.genten ) { cc.tmp.circle( 0, 0, 2, "red", null ); } else if( this.debug.joint ) { if( joint == this.selfJoint ) { //親と接続するジョイント cc.tmp.circle( 0, 0, 2, "magenta" ); } else { //その他のジョイント cc.tmp.circle( 0, 0, 3, null, "green" ); } } if( this.debug.jointname ) { cc.font = "6px''"; cc.fillStyle = "blue"; cc.fillText( this.name + "." + name, 8, this.debug.jointnameY ); } cc.restore(); }//for }//debug cc.restore(); }//draw() //-- Method mathRotate (SVC_Part 11/12) mathRotate( x, y, theta2 ) { let theta1 = Math.atan2( y, x ); let hankei = Math.sqrt( x * x + y * y ); return { X : Math.cos( theta1 + theta2 ) * hankei, Y : Math.sin( theta1 + theta2 ) * hankei, } } //-- Method mathRotateC (SVC_Part 12/12) mathRotateC( cx, cy, x, y, theta2, flg ) { x -= cx; y -= cy; let theta1 = Math.atan2( y, x ); let hankei = Math.sqrt( x * x + y * y ); return { X : Math.cos( theta1 + theta2 ) * hankei + cx, Y : Math.sin( theta1 + theta2 ) * hankei + cy, } } }//class SVC_Part //--Class 3/8 Joint class Joint { constructor( name, initialX, initialY ) { this.name = name; this.initialX = initialX; this.initialY = initialY; this.absoluteX = 0; this.absoluteY = 0; } } //--Class 4/8 Poses class Poses { constructor() { this.array = new Array(); } //-- Method add (Poses 1/2) add( name, pose ) { this.array.push( pose ); } //-- Method exportToAnms (Poses 2/2) exportToAnms() { //poseの配列からanmを作りそれぞれをつなげる let firstAnmGroup; let beforeAnmGroup; //poseの配列について this.array.map( //1つのposeについて pose => { //poseが持つ一連のアニメを取り出し、 let anmGroup = pose.getAnmGroup( name ); if( ! beforeAnmGroup ) { //1つ目のposeの場合、それをトップとする firstAnmGroup = anmGroup; } else { //1つ目以降のposeの場合、トップの下へチェーン状に連なる beforeAnmGroup.next = anmGroup; } beforeAnmGroup = anmGroup; } ); //トップを返す return firstAnmGroup; //トップの下へ連なるものは、トップが終わったら次、それが終わったら次、 //と順次、アニメを連続して行っていく。 }//toAnms() }//class Poses //--Class 5/8 Pose class Pose { constructor( ms, timerMs, anmdata ) { this.ms = ms; this.timerMs = timerMs; this.anmdata = anmdata; this.next = null; } //-- Method getAnmGroup (Pose 1/1) getAnmGroup( name ) { let anmGroup = new AnmGroup(); anmGroup.name = name; let tmpArray = new Array(); for( let i = 0; i < this.anmdata.list.length; i++ ) { let data = this.anmdata.list[ i ]; let object = data[ 0 ]; let propertyName = data[ 1 ]; //check. オブジェクトとプロパティの組み合わせの重複を確認 let res = true; for( let i = 0; i < tmpArray.length; i++ ) { if( tmpArray[ i ][ 0 ] == object && tmpArray[ i ][ 1 ] == propertyName ) { res = false; break; } } if( !res ) { alert( "「同時に開始されるアニメ」のリスト(" + name + ")の中で、同じオブジェクトにおいて、同じプロパティ名(" + propertyName + ")でのアニメが指定されています. 競合するので直してください. at class:Pose method:getAnmGroup()" ); } let endValue = data[ 2 ]; let anm = new Anm( object, propertyName, endValue, this.ms, this.timerMs ); anmGroup.children.push( anm ); //check. 開始・終了メソッド定義 if( this.anmdata.start ) anmGroup.start = this.anmdata.start; if( this.anmdata.end ) anmGroup.end = this.anmdata.end; tmpArray.push( [ object, propertyName ] ); }//for return anmGroup; }//getAnmGroup() }//class Pose //--Class 6/8 Anm class Anm { constructor( object, propertyName, endValue, ms, timerMs ) { this.name = "**"; this.class = "Anm"; this.object = object; this.propertyName = propertyName; //check. 指定ミスチェック if( this.propertyName in this.object == false ) { alert( "プロパティ" + this.propertyName + "はオブジェクト this.object のメンバではありません. at Anm.constructor" ); } //アニメの設定値(終了値)について、アニメ定義時の値か、アニメ実行時の値か、 if( typeof endValue === "string" ) { //アニメ実行時の値(参照値) this.endValuePropertyName = endValue; } else { //アニメ定義時の値(即値) this.endValue = endValue; } this.ms = ms; this.timerMs = timerMs; this.ready(); } //-- Method ready (Anm 1/4) ready() { //アニメを準備する //start値セット this.startValue = this.object[ this.propertyName ]; //end値セット if( typeof this.endValuePropertyName !== "undefined" ) this.endValue = this.object[ this.endValuePropertyName ]; this.endFlg = this.endValue == this.startValue; this.dir = this.endValue > this.startValue ? 1 : -1; let count = this.ms / this.timerMs; this.step = Math.abs( this.endValue - this.startValue ) / count; } //-- Method frame (Anm 2/4) frame() { this.object[ this.propertyName ] += this.step * this.dir; //check. if( ( this.dir > 0 && this.object[ this.propertyName ] >= this.endValue ) || ( this.dir < 0 && this.object[ this.propertyName ] <= this.endValue ) ) { this.object[ this.propertyName ] = this.endValue; this.endFlg = true; } } //-- Method start (Anm 3/4) start() {} //-- Method end (Anm 4/4) end() {} }//class Anm //--Class 7/8 AnmGroup class AnmGroup { constructor( name ) { this.name = name; this.class = "AnmGroup"; this.children = new Array(); this.next = null; } //-- Method frame (AnmGroup 1/5) frame() { //子のframe()を実行する。 let allEnd = true; this.children.map( child => { child.frame(); //check. if( child.endFlg == false ) allEnd = false; } ); this.endFlg = allEnd; } //-- Method ready (AnmGroup 2/5) ready() { //子のready()を実行する。 this.children.map( child => child.ready() ); } //-- Method getNext (AnmGroup 3/5) getNext() { //check. if( ! this.next ) return null; this.next.ready(); return this.next; } //-- Method start (AnmGroup 4/5) start() {} //-- Method end (AnmGroup 5/5) end() {} }//class AnmGroup //--Class 8/8 GraphPaper class GraphPaper extends SVC_Part { constructor( name, args ) { super( name ); //P点 this.jointP = this.addJoint( "jointP", 0, 0 ); //初期値 this.f = function( x ) { return x; }; this.sx = -10; //グラフ上の始点の x 座標 this.ex = 10; //グラフ上の終点の x 座標 this.px = 0; //グラフ上の点 P の x 座標 this.zoomX = 1; //x方向だけ引き伸ばしたいときに利用 this.zoomY = 1; //y方向だけ引き伸ばしたいときに利用 this.zoom = 1; //全体的に拡大したいときに利用 this.visibility = false; //計算には用いないが、表示で使用する this.sy = 0; //グラフ上の始点の y 座標 this.ey = 0; //グラフ上の終点の y 座標 this.py = 0; //グラフ上の点 P の y 座標 //別のインスタンスへの変更を禁止 Object.seal( this.joints.jointP ); Object.seal( this.jointP ); //このインスタンスのプロパティを args の内容で上書き if( args ) this.setting( args ); }//constructor() //this.pxに値を代入するだけで、同時に jointP が変更されるしくみ set px( x ) { this._px = x; let res = this.graphXToScreenXY( x ); this.jointP.initialX = res.x; this.jointP.initialY = res.y; } get px() { return this._px; } //-- Method setting (GraphPaper 1/3) setting( args ) { //このインスタンスのプロパティを args の内容で上書き Object.assign( this, args ); this.px = this.sx; //グラフ上のP点のx座標。y座標は都度計算。 } //-- Method graphXToScreenXY (GraphPaper 2/3) //数学の関数 f はここで使用されている。 graphXToScreenXY( graphX ) { return { x : ( graphX - this.sx ) * this.zoomX * this.zoom, y : ( this.f( graphX ) - this.f( this.sx ) ) * this.zoomY * this.zoom, }; } //-- Method drawImage (GraphPaper 3/3) drawImage( cc ) { //グラフの概形を画面に描く for( let graphX = this.sx; graphX <= this.ex; graphX++ ) { let sc = this.graphXToScreenXY( graphX ); //check. 原点のとき、座標軸を描く if( graphX == 0 ) { cc.tmp.line( sc.x - 50, -sc.y, sc.x + 50, -sc.y, "lightgray" ); cc.tmp.line( sc.x, -sc.y - 50, sc.x, -sc.y + 50, "lightgray" ); } //薄い青い点・ cc.tmp.circle( sc.x, -sc.y, 1, "lightblue" ); } //P点 if( 1 ) { let sc = this.graphXToScreenXY( this.px ); cc.tmp.circle( sc.x, -sc.y, 3, null, "orange" ); } //ガイドライン if( 1 ) { cc.font = "6px ''"; cc.tmp.circle( this.sx, -this.sy, 2, "red" ); cc.fillText( "放物線 始点指定", this.sx + 5, -this.sy + 6 ); cc.tmp.circle( this.cx, -this.cy, 2, "red" ); cc.fillText( "放物線 高さ指定", this.cx, -this.cy - 5 ); cc.tmp.circle( this.ex, -this.ey, 2, "red" ); cc.fillText( "放物線 終点指定", this.ex + 5, -this.ey + 6 ); } }//draw() }//class GraphPaper