Skin:
[NORMAL]
[BLUE] [DOS] [LIGHT]  / コピーするための表示 / 実行
このファイル: /home/web6047/www/cgi-bin/prj/20180801-indexJS/a.js
1 /*
2 どうなっているのか?
3
4 App()
5 App.onloadx() htmlのonloadで呼ばれる。画像のプリロードからも呼ばれ、loadをカウントしてる。
6 App.onloadx1() htmlとimageのloadが完了したら呼ばれる。固定的な内容。onloadx2を呼ぶ。
7 App.onresizex() 固定的な内容。通常は変更しないと思う。
8 App.onloadx2() ★ここから書く★
9 App.start() stopをクリックした後再開するとき呼ばれる。
10 App.run() 書いたもの。消しても良い
11 App.draw() 書いたもの。消しても良い
12
13 【説明】
14
15 このファイルを html ファイルの script タグの src 属性で指定するだけで、動作開始するように作ったつもり。
16 目的のソフトウェアを App というクラスにまとめてある。
17 コメントで、「以下 変更しない部分」、「以下 変更する部分」のようにファイルを大きく2分してある。
18 このファイルを再利用するためです。
19
20 html の読み込みが完了した時点で、App クラスの onloadx1() 関数が呼ばれる。
21
22 onloadx1() と onloadx2() は処理的に分ける必要はないが、変更する必要のない部分と、
23 目的のソフトウェアを実装する部分とに分別するために分けた。
24
25 リサイズされたら、目的の部分(canvas 要素)はサイズを自動で合わせるようになっている。
26
27
28 【目的のソフトウェア 部分】
29
30 ページのロードが完了すると、このプログラムは画面に大きく、多数の四角形の 3DCG を描画する。
31 右上の STOP ボタンで停止と再開を行う。
32
33 アニメを行う基本的なプログラムになっている。
34
35 onloadx2() で 3DCG の各モデルの定義を行い、その後すぐに draw() している。
36 つづく start() でアニメが開始される。( setInterval() による run() の定期実行を行う)
37
38 run() は各アニメの数値の推移を行い、
39 "すい星"(大きな四角形の後に小さな四角形がパラパラと続く様子)
40 の尾の作成、消去など行い、最後に draw() している。
41
42 draw() は 3DCG の描画の基本的なプログラムが書かれている。
43
44 1. 画面に描く各四角形の4頂点の座標計算
45 2. 奥から手前の順になるようにソート(画家のアルゴリズム)
46 3. 描画
47
48 webGL やその他 3DCG などのライブラリは一切使用していません。
49
50
51 */
52
53 //---以下 変更しない部分
54
55 /*
56 <tag1 id="whiteareaID"> → relative, z-index=0; に変更される
57 <div>STOPやSPEEDなどのリンク</div> 自動作成される
58 <canvas></canvas> 自動作成される
59 </tag1>
60 */
61
62 var indexjs = new App();
63 App.prototype.onloadx = function( e ) {
64 this.onloadxCount++;
65 console.log( this.onloadxCount + " / " + this.onloadxCountMax );
66 //check.
67 if( this.onloadxCount == this.onloadxCountMax ) {
68 var parentEL = document.getElementById( "whiteareaID" );
69 this.onloadx1( parentEL ); //onloadx1は最後にonloadx2を呼んでいる
70 }
71 };
72
73 //ページ読み込み完了で開始
74
75 //---画像
76
77 indexjs.images = {
78 "yuyake" : "20180801-indexJS/imgs/Sheet1.png",
79 "gaito" : "20180801-indexJS/imgs/Sheet2.png",
80 }
81 indexjs.onloadxCount = 0;
82 indexjs.onloadxCountMax = Object.keys( indexjs.images ).length + 1;
83 for( var name in indexjs.images ) {
84 var src = indexjs.images[ name ];
85 indexjs.images[ name ] = new Image();
86 indexjs.images[ name ].onload = indexjs.onloadx.bind( indexjs );
87 indexjs.images[ name ].src = src;
88 }
89 addEventListener( "load", indexjs.onloadx.bind( indexjs ) );
90
91
92 function App() { //Appクラスのコンストラクタ
93 this.name = "test";
94 }
95
96 App.prototype.onloadx1 = function( parentEL ) {
97 console.log( "onloadx1" );
98 //(※正直言うとcanvasのサイズについて多少混乱中…)
99
100 //check.
101 if( ! parentEL ) parentEL = document.getElementsByTagName( "body" )[ 0 ];
102
103 //canvasを置く親要素
104 this.parentEL = parentEL;
105 with( this.parentEL.style ) {
106 position = "relative";
107 zIndex = 0;
108 }
109
110 //アニメの停止ボタン 親要素の上部に配置
111 this.swEL = document.createElement( "div" );
112 this.parentEL.appendChild( this.swEL );
113 with( this.swEL.style ) {
114 border = "solid 0px black";
115 position = "absolute";
116 right = "0px";
117 top = "0px";
118 boxSizing = "border-box";
119 zIndex = 2;
120 padding = ".5em 1em";
121 }
122 this.swEL.innerHTML = "";
123 this.swEL.innerHTML += "<a style='color:black;' href=\"javascript:if( indexjs.timerID ) indexjs.stop(); else indexjs.start(); void(0);\">STOP</a><BR>";
124 this.swEL.innerHTML += '<a style=\'color:black;\' href=%%dquote20%%>SPEED</a>';
125
126 //CANVAS要素 親要素の上部に配置
127 this.canvasEL = document.createElement( "canvas" );
128 this.parentEL.appendChild( this.canvasEL );
129 this.canvasEL.setAttribute( "id", "indexjs_canvasELID" );
130 with( this.canvasEL.style ) {
131 border = "solid 0px black";
132 position = "absolute";
133 left = "0px";
134 top = "0px";
135 boxSizing = "border-box";
136 zIndex = -1;
137
138 }
139
140
141 //true にするとドットがシャープになる。falseはアンチエイリアスが入る(通常)。IEは非対応
142 if( true ) {
143 this.canvasEL.style.imageRendering = "pixelated";
144 this.canvasEL.style.imageRendering = "optimizeSpeed";
145 }
146 this.lowmode = false;
147 this.cc = this.canvasEL.getContext( "2d" );
148
149 this.onresizex();
150
151 //resize設定
152 addEventListener( "resize", ( function( e ) { this.onresizex( e ); } ).bind( this ), false );
153
154 //canvasが画面外に出たらCPUパワーを抑えるために停止する処理
155 addEventListener( "scroll", function( e ) {
156
157 var scrollEL = document.documentElement ? document.documentElement : document.body;
158 var r = indexjs.cc.canvas.getBoundingClientRect();
159
160 if( scrollEL.scrollTop + r.top + r.height / 2 < 0 )
161 indexjs.stop();
162 else
163 if( ! indexjs.timerID ) indexjs.start();
164
165 }, false );
166
167 this.onloadx2();
168
169 }//onloadx1
170
171 App.prototype.onresizex = function( e ) {
172
173 //リサイズされた親要素に合わせて、サイズ変更
174
175 var wa = this.parentEL;
176 var waRect = wa.getBoundingClientRect();
177 this.canvasW = waRect.width;
178
179 //最初のh2タグに合わせる
180 var htags = document.getElementsByTagName( "h2" );
181 if( htags.length > 0 ) {
182 this.canvasH = htags[ 0 ].getBoundingClientRect().top - wa.getBoundingClientRect().top - 128;
183 } else {
184 this.canvasH = 480;
185 }
186
187 this.canvasEL.style.width = this.canvasW + "px";
188 this.canvasEL.style.height = this.canvasH + "px";
189 this.canvasEL.setAttribute( "width", this.canvasW );
190 this.canvasEL.setAttribute( "height", this.canvasH );
191
192
193 //true にすると解像度を下げる。false は通常。
194 if( this.lowmode ) {
195 var pixelsize = 2; //ドットの大きさ2~
196 this.canvasEL.style.width = this.canvasEL.width + "px"; //実物画面大きさとして
197 this.canvasEL.style.height = this.canvasEL.height + "px";
198 this.canvasEL.width /= pixelsize; //解像度として
199 this.canvasEL.height /= pixelsize;
200 this.cc.scale( 1 / pixelsize, 1 / pixelsize );
201 }
202
203 //ビルのシルエット作成
204 this.building = new Array();
205 var x = 0;
206 with( this ) {
207 var takasaMax = canvasH - 8;
208 var takasaMin = canvasH - 64;
209 var habaMax = 32;
210 var habaMin = 8;
211 this.building.push( { x : canvasW, y : canvasH } );
212 this.building.push( { x : 0, y : canvasH } );
213 while( 1 ) {
214 var takasa = Math.random() * ( takasaMax - takasaMin ) + takasaMin;
215 this.building.push( { x : x, y : takasa } );
216 var haba = Math.random() * ( habaMax - habaMin ) + habaMin;
217 x += haba;
218 //check.
219 if( x >= canvasW ) {
220 this.building.push( { x : canvasW, y : takasa } );
221 break;
222 }
223 this.building.push( { x : x, y : takasa } );
224 }
225 }
226
227
228 if( e ) this.draw();
229
230 }//onresizex
231
232 //---以上 変更しない部分
233
234
235 //---以下 変更する部分 目的のソフトウェア
236
237 App.prototype.onloadx2 = function() {
238 console.log( "onloadx2" );
239 //like DirectX
240
241 ///---allMaster
242
243 this.allMaster = {
244 type : "normal",
245 name : "noname",
246 tens : null,
247 mens : null,
248 fillStyle : "salmon",
249 strokeStyle : "black",
250 kaitenX : 0,
251 kaitenY : 0,
252 kaitenZ : 0,
253 kaitenStep : .05,
254 zm : 100,
255 pos : {
256 x : 0,
257 y : 0,
258 z : 200,
259 },
260 parent : null,
261 visibility : true,
262 };
263
264 ///---cubeMaster
265
266 this.cubeMaster = objcopy( this.allMaster );
267
268 with( this.cubeMaster ) {
269 type = "cube";
270 tens = [
271 { x : -1, y : +1, z : -1 }, //手前 左上点 0
272 { x : +1, y : +1, z : -1 }, //手前 右上点 1
273 { x : +1, y : -1, z : -1 }, //手前 右下点 2
274 { x : -1, y : -1, z : -1 }, //手前 左下点 3
275 { x : -1, y : +1, z : +1 }, //奥 左上点 4
276 { x : +1, y : +1, z : +1 }, //奥 右上点 5
277 { x : +1, y : -1, z : +1 }, //奥 右下点 6
278 { x : -1, y : -1, z : +1 }, //奥 左下点 7
279 ];
280 mens = [
281 [ 0, 1, 2, 3 ], //前面
282 [ 1, 5, 6, 2 ], //右面
283 [ 4, 0, 3, 7 ], //左面
284 [ 4, 5, 1, 0 ], //上面
285 [ 3, 2, 6, 7 ], //下面
286 [ 5, 4, 7, 6 ], //背面
287 ];
288 fillStyle = "";
289 };
290
291 ///---hanabiraMaster
292
293 this.hanabiraMaster = objcopy( this.allMaster );
294 with( this.hanabiraMaster ) {
295 type = "hanabira";
296 tens = [
297 { x : 0, y : 4, z : 0 }, //花びらスリット部
298
299 { x : 1, y : 7, z : 0 },
300 { x : 3, y : 5, z : 0 },
301 { x : 4, y : 1, z : 0 }, //花びら右端
302 { x : 3, y : -3, z : 0 },
303
304 { x : 0, y : -6, z : 0 }, //花びら根元部
305
306 { x : -3, y : -3, z : 0 },
307 { x : -4, y : 1, z : 0 }, //花びら左端
308 { x : -3, y : 5, z : 0 },
309 { x : -1, y : 7, z : 0 },
310 ];
311 mens = [
312 [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], //上記頂点をこの順で描いて面とする
313 ];
314 zm = .5;
315 strokeStyle = "";
316 }
317
318 ///---areaMaster
319
320 this.areaMaster = objcopy( this.cubeMaster );
321 with( this.areaMaster ) {
322 zm = 1;
323 visibility = false; //エリアを示すワイヤフレームは最初見えない
324 pos.x = 30;
325 pos.z = 450;
326 fillStyle = "";
327 strokeStyle = "pink"; //ワイヤフレームの色(後で上書きされている)
328 kaitenZ = 3.14 / 16; //左にやや傾ける
329 kaitenStep = .025;
330 }
331 //独自メンバを新規作成
332 this.areaMaster.hankei = 300; //花びらが配置される筒状の半径
333 this.areaMaster.takasa = this.canvasH / 1; //筒状の高さ
334 this.areaMaster.holeHankeiRate = .5; //ドーナツ状にくりぬく分の割合
335 this.areaMaster.holeHankei = null;
336 this.areaMaster.countOfHanabira = 100; //花びらの数(後で上書きされている)
337 this.areaMaster.hanabira = null;
338
339 //---areaの作成
340
341 this.areas = new Array();
342
343 switch( 0 ) {
344 case 0: //ノーマル
345
346 this.bgmode = 2; //夕焼け
347 this.timerMS = 50;
348
349 this.cam = new Object();
350 this.cam.s = 62; //焦点距離
351 this.cam.zm = 14.25; //引き伸ばし
352
353 //エリアは2個
354 for( var i = 0; i < 2; i++ ) {
355 this.areas[ i ] = objcopy( this.areaMaster );
356 with( this.areas[ i ] ) {
357 switch( i ) {
358 case 0:
359 countOfHanabira = 130;
360 pos.x = 140;
361 kaitenX = 0.174;
362 strokeStyle = "white"; //ワイヤフレーム表示時のワイヤ色
363 break;
364 case 1:
365 //エリア2は花びらの数20%、全体の傾き20%
366 countOfHanabira = Math.round( this.areas[ 0 ].countOfHanabira * .2 );
367 kaitenZ = this.areas[ 0 ].kaitenZ - .2;
368 strokeStyle = "magenta";
369 break;
370 }
371 }
372 }
373 break;
374
375 }//switch
376
377 var areaChange = function() {
378
379 //エリアのサイズが変更されたら、頂点も変更する。
380 for( var j = 0; j < this.areas.length; j++ ) {
381 var area = this.areas[ j ];
382 area.holeHankei = area.hankei * area.holeHankeiRate;
383 area.tens = objcopy( this.areaMaster.tens ); //初期化
384 for( var i = 0; i < area.tens.length; i++ ) {
385 var ten = area.tens[ i ];
386 ten.x *= area.hankei;
387 ten.y *= area.takasa / 2;
388 ten.z *= area.hankei;
389 }
390 }
391
392 }.bind( this );
393
394 areaChange();
395
396
397 onkeydown = function( e ) {
398 //イベントだが、this は indexjs になっている
399
400 //---キー入力
401
402 switch( e.which ) {
403 case 32: //space stop時コマ送り
404 e.preventDefault();
405 e.stopPropagation();
406 //check.
407 if( this.timerID ) break;
408 this.run();
409 return false;
410 default:
411 }
412
413 switch( String.fromCharCode( e.which + 32 ) ) {
414 case "q": this.cam.s++; break; //焦点距離増減
415 case "a": this.cam.s--; break;
416 case "o": //背景切り替え
417 this.bgmode += 1;
418 if( this.bgmode >= this.bgs.length ) this.bgmode = 0;
419 break;
420 case "z": this.lowmode = !this.lowmode; this.onresizex(); break; //背景切り替え
421 case "l": //設定値出力
422 for( var name in this.cam ) console.log( "this.cam:", name, this.cam[ name ] );
423 break;
424 default:
425 console.log( "ascii:", String.fromCharCode( e.which + 32 ), "e.which:", e.which );
426 }
427
428 for( var i = 0; i < this.areas.length; i++ ) {
429 var area = this.areas[ i ];
430 switch( String.fromCharCode( e.which + 32 ) ) {
431 case "p": area.visibility = ! area.visibility; break;//エリア枠の表示
432 case "w": area.kaitenX += area.kaitenStep; break; //エリア枠をX軸回転
433 case "s": area.kaitenX -= area.kaitenStep; break;
434 case "e": area.kaitenY += area.kaitenStep; break; //エリア枠をY軸回転
435 case "d": area.kaitenY -= area.kaitenStep; break;
436 case "r": area.kaitenZ += area.kaitenStep; break; //エリア枠をZ軸回転
437 case "f": area.kaitenZ -= area.kaitenStep; break;
438 case "t": area.hankei += 10; areaChange(); this.reset(); break; //エリア枠の幅を増
439 case "g": area.hankei -= 10; areaChange(); this.reset(); break;
440 case "y": area.takasa += 10; areaChange(); this.reset(); break; //エリア枠の高さを増減
441 case "h": area.takasa -= 10; areaChange(); this.reset(); break;
442 case "i": area.pos.z += 10; this.reset(); break; //ijkmの十字キーで、エリア枠を水平方向に移動
443 case "j": area.pos.x -= 10; this.reset(); break;
444 case "k": area.pos.x += 10; this.reset(); break;
445 case "m": area.pos.z -= 10; this.reset(); break;
446 case "l": //設定値出力
447 console.log( "areas[ " + i + " ].hankei:", area.hankei );
448 console.log( "areas[ " + i + " ].takasa:", area.takasa );
449 for( var name in area ) console.log( "areas[ " + i + " ]:", name, area[ name ] );
450 for( var name in area.pos ) console.log( "areas[ " + i + " ].pos:", name, area.pos[ name ] );
451 break;
452 default:
453 }//switch
454 }//for
455
456 this.draw();
457 }.bind( this );
458
459 //---背景描画設定
460
461 this.bgs = new Array();
462
463 this.bgs[ 0 ] = function() { //透明(白)背景
464 this.cc.clearRect( 0,0, this.canvasW, this.canvasH );
465 }.bind( this );
466
467 this.bgs[ 1 ] = function() { //青空と草原
468 with( this.cc ) {
469 var horizontal = .75;
470 var skyHeight = this.canvasH * horizontal;
471 var roundHeight = this.canvasH - skyHeight;
472
473 fillStyle = "blue";
474 fillRect( 0,0, this.canvasW, skyHeight );
475
476 fillStyle = "green";
477 fillRect( 0,skyHeight, this.canvasW, roundHeight );
478
479 if( 0 ) {
480 //地平線の白
481 var w = 16;
482 var grad = this.cc.createLinearGradient( 0, skyHeight - w, 0, skyHeight + w );
483 grad.addColorStop( 0, 'rgba( 0, 0, 0, 0 )' );
484 grad.addColorStop( 0.5, 'rgba( 255,255,255, .5 )' );
485 grad.addColorStop( 1, 'rgba( 0, 0, 0, 0 )' );
486 this.cc.fillStyle = grad;
487 this.cc.fillRect( 0, skyHeight - w, this.canvasW, skyHeight + w );
488 }
489
490 }//with
491 }.bind( this );//function
492
493 this.bgs[ 2 ] = function() { //ゆうやけ
494 this.cc.clearRect( 0,0, this.canvasW, this.canvasH );
495 this.cc.drawImage( this.images.yuyake, 0,0,this.canvasW, this.canvasH );
496 with( this.cc ) {
497 beginPath();
498 for( var i = 0; i < this.building.length; i++ ) {
499 var p = this.building[ i ];
500 if( i == 0 )
501 moveTo( p.x, p.y );
502 else
503 lineTo( p.x, p.y );
504 }
505 closePath();
506 fillStyle = "black";
507 fill();
508 }
509 }.bind( this ); //function
510
511 this.bgs[ 3 ] = function() { //夜桜
512 var bottom = 16;
513 this.cc.fillStyle = "black";
514 this.cc.fillRect( 0,0, this.canvasW, this.canvasH - bottom );
515
516 //街灯の電球の位置を基準にする
517 var denkyuX = this.canvasW / 5;
518 var denkyuY = this.canvasH / 4;
519
520 //円形グラデ 街灯の照らし
521 var r0 = 18;
522 var x0 = denkyuX; //電球付近から
523 var y0 = denkyuY;
524
525 //電球の明るいのが及ぶ部分へ
526 var r1 = 150;
527 var x1 = x0 + Math.floor( Math.sqrt( ( r1 * r1 ) / 2 ) ) - Math.ceil( Math.sqrt( ( r0 * r0 ) / 2 ) );
528 var y1 = y0 + Math.floor( Math.sqrt( ( r1 * r1 ) / 2 ) ) - Math.ceil( Math.sqrt( ( r0 * r0 ) / 2 ) );
529
530 var grad = this.cc.createRadialGradient(x0, y0, r0, x1, y1, r1);
531 grad.addColorStop( 0, 'white' );
532 grad.addColorStop( 0.5, 'rgba(0,0,0,0)' );
533 grad.addColorStop( 1, 'black' );
534 this.cc.fillStyle = grad;
535 this.cc.fillRect(0,0, this.canvasW,this.canvasH);
536
537
538 //街灯から離れた暗がり(後描画)
539 this.drawNightForeground = function() {
540
541 var r2 = 8;
542 var x2 = denkyuX; //この円から
543 var y2 = denkyuY;
544
545 var r3 = this.canvasW / 2;
546 var x3 = x2 + this.canvasW / 3.5; //次の円
547 var y3 = y2;
548
549 var grad = this.cc.createRadialGradient(x2, y2, r2, x3, y3, r3);
550 grad.addColorStop( 0, 'rgba(0,0,0,0)' );
551 grad.addColorStop( .75, 'rgba(0,0,0,0)' );
552 grad.addColorStop( 1, 'rgba(0,0,0,.95)' );
553 this.cc.fillStyle = grad;
554 this.cc.fillRect(0,0, this.canvasW,this.canvasH);
555
556 //線形グラデ 下部 ドキュメントの白へのグラデ
557 this.cc.beginPath();
558 var grad = this.cc.createLinearGradient( 0, this.canvasH - bottom, 0, this.canvasH );
559 grad.addColorStop( 0, 'rgba(0,0,0,0)' );
560 grad.addColorStop( 1, 'white' );
561 this.cc.fillStyle = grad;
562 this.cc.fillRect( 0, this.canvasH - bottom, this.canvasW, bottom );
563
564 //夜桜です、という表示
565 this.cc.fillStyle = "white";
566 this.cc.fillText( "夜桜", this.canvasW - 48, this.canvasH - 32 );
567
568 };//function
569
570 //街灯画像
571 this.cc.drawImage( this.images.gaito, denkyuX - 80, denkyuY - 22 );
572
573 }.bind( this );//function
574
575 this.onloadx3();
576 };
577
578 App.prototype.onloadx3 = function() {
579
580 this.models = new Array();
581
582 //---hanabiraの作成
583
584 for( var j = 0; j < this.areas.length; j++ ) {
585 var area = this.areas[ j ];
586 area.hanabiras = new Array();
587
588 for( var i = 0; i < area.countOfHanabira; i++ ) {
589 var hanabira = objcopy( this.hanabiraMaster );
590
591 if( 1 ) {
592 //正六面体状に配置
593 var ry = Math.floor( Math.random() * area.takasa ) - area.takasa / 2;
594 var hankei = area.hankei;
595 var holeHankei = hankei * 0.5;
596 var ringW = hankei - holeHankei;
597
598 var a = Math.floor( Math.random() * 3 );
599 var b = Math.floor( Math.random() * 4 );
600 var rx = Math.floor( Math.random() * ringW ) + ( (a==1 || a== 2) ? (holeHankei) : 0 );
601 var rz = Math.floor( Math.random() * ringW ) + ( (a==0 || a== 1) ? (holeHankei) : 0 );
602 rx *= ( b == 1 || b == 2 ) ? -1 : 1;
603 rz *= ( b == 2 || b == 3 ) ? -1 : 1;
604 } else {
605 //円筒形状に配置
606 var baumkuchenW = area.hankei - area.holeHankei; //バームクーヘンの太さ
607 var ry = Math.round( Math.random() * area.takasa ) - area.takasa / 2;
608
609 //バームクーヘン状にちりばめる
610 var theta = Math.random() * 6.28;
611 var hankei2 = Math.random() * baumkuchenW + area.holeHankei;
612 var rx = Math.cos( theta ) * hankei2
613 var rz = Math.sin( theta ) * hankei2;
614 }
615
616 hanabira.pos = {
617 x : area.pos.x + rx,
618 y : area.pos.y + ry,
619 z : area.pos.z + rz,
620 };
621 hanabira.kaitenY = Math.random() * 6.28;
622 hanabira.kaitenX = Math.random() * 6.28;
623 hanabira.parent = area;
624
625 hanabira.fillStyle = j == 0 ? "rgb(255,240,240)" : "rgb(255,210,210)";
626
627 area.hanabiras.push( hanabira );
628 }
629 this.models.push( area );
630 this.models = this.models.concat( area.hanabiras );
631 }
632
633
634 this.draw();
635 this.start();
636 }
637
638 App.prototype.start = function() {
639 this.timerID = setInterval( this.run.bind( this ), this.timerMS );
640 };
641 App.prototype.stop = function() {
642 if( this.timerID ) {
643 clearInterval( this.timerID );
644 this.timerID = null;
645 }
646 }
647 /*
648 this.hanabirasを
649 this.areas[ 0 ].hanabirasにしたところ。
650
651 花びら作成をエリア対応に。
652 エリアごとの花びら数を調整。
653 花びら描画をエリア対応に。
654 作成した花びらをmodelsへ追加するところエリア対応。
655 */
656 App.prototype.reset = function() {
657 this.stop();
658 this.onloadx3();
659 }
660 App.prototype.run = function() {
661 //それぞれの回転を進める
662 for( var j = 0; j < this.areas.length; j++ ) {
663 this.areas[ j ].kaitenY += this.areas[ j ].kaitenStep;
664 }
665 for( var i = 0; i < this.areas[ 0 ].hanabiras.length; i++ ) {
666 var hanabira = this.areas[ 0 ].hanabiras[ i ];
667 hanabira.kaitenY += .05;
668 hanabira.kaitenX += .05;
669 }
670 this.draw();
671 };
672
673 App.prototype.draw = function() {
674
675 //背景描画
676 this.bgs[ this.bgmode ]();
677
678 this.cc.save();
679 this.cc.translate( this.canvasW / 2, this.canvasH / 2 );
680 this.cc.scale( 1, -1 );
681
682
683 //都合により頂点計算部分を関数へ分離
684
685 var tenkeisan = function( model ) {
686 //check.
687 if( ! model.visibility ) return;
688
689 model.tensC = new Array();
690
691 //modelは、はなびらと、エリア枠も含まれる。(汎用的)
692 for( var i = 0; i < model.tens.length; i++ ) {
693 var ten = model.tens[ i ];
694 var x = ten.x;
695 var y = ten.y;
696 var z = ten.z;
697
698 x *= model.zm;
699 y *= model.zm;
700 z *= model.zm;
701
702 //自転
703 //kaitenY
704 var k = kaiten( x, z, model.kaitenY );
705 x = k.X;
706 z = k.Y;
707
708 //kaitenX
709 var k = kaiten( z, y, model.kaitenX );
710 z = k.X;
711 y = k.Y;
712
713 //kaitenZ
714 var k = kaiten( x, y, model.kaitenZ );
715 x = k.X;
716 y = k.Y;
717
718 x += model.pos.x;
719 y += model.pos.y;
720 z += model.pos.z;
721
722 //エリアが回転すると、エリアを親とする花びらたちも回転する
723 if( model.parent != null ) {
724 var p = model.parent;
725
726 //kaitenY
727 var k = kaiten2( p.pos.x, p.pos.z, x, z, p.kaitenY );
728 x = k.X;
729 z = k.Y;
730
731 //kaitenX
732 var k = kaiten2( p.pos.z, p.pos.y, z, y, p.kaitenX );
733 z = k.X;
734 y = k.Y;
735
736 //kaitenZ
737 var k = kaiten2( p.pos.x, p.pos.y, x, y, p.kaitenZ );
738 x = k.X;
739 y = k.Y;
740 }
741
742 //3Dを2D化
743 var h = x * ( this.cam.s / z ) * this.cam.zm;
744 var v = y * ( this.cam.s / z ) * this.cam.zm;
745
746
747 var tenC = {
748 x : x,
749 y : y,
750 z : z,
751 h : h,
752 v : v,
753 };
754 model.tensC[ i ] = tenC;
755 }
756 }.bind( this );//function
757
758
759 //頂点を計算済みにする
760 for( var j = 0; j < this.models.length; j++ ) {
761 tenkeisan( this.models[ j ] );
762 }
763
764 forK:for( var k = 0; k < this.models.length; k++ ) {
765 var model = this.models[ k ];
766 //check.
767 if( ! model.visibility ) continue;
768
769 for( var j = 0; j < model.mens.length; j++ ) {
770 var men = model.mens[ j ];
771
772 var overZ = false;
773 var points = new Array();
774
775 this.cc.beginPath();
776 for( var i = 0; i < men.length; i++ ) {
777 var tenIdx = men[ i ];
778 var tenC = model.tensC[ tenIdx ];
779
780 //check. 視点の背後に触れた
781 if( tenC.z <= 0 ) {
782 overZ = true;
783 break;
784 }
785
786 var h = tenC.h;
787 var v = tenC.v;
788
789 if( i == 0 )
790 this.cc.moveTo( h, v );
791 else
792 this.cc.lineTo( h, v );
793
794 points.push( tenC );
795 }
796 this.cc.closePath();
797
798 //check. 視点の背後に触れたものは描かない。
799 if( overZ ) continue;
800
801 //check. 線状に見える角度の花びらを正面向きに直す。視線と面の向きの内積で、線状に見えてるのかどうか判断
802 if( model.type == "hanabira" ) {
803 var housen = getHousen( points );
804 var sisen = toNorm( {
805 x : -points[ 1 ].x,
806 y : -points[ 1 ].y,
807 z : -points[ 1 ].z,
808 } );
809 var cosTheta = naiseki( housen, sisen );
810 var limit = .3; //線状に見えるとする幅
811 if( cosTheta > -limit && cosTheta < limit ) {
812 model.kaitenY = Math.random() * 6.28;
813 model.kaitenX = Math.random() * 6.28;
814
815 //このモデルを新しい角度で計算し直し
816 tenkeisan( model );
817 k--;
818 continue forK;
819 }
820 }
821
822
823 //描く
824 if( model.fillStyle ) {
825 this.cc.fillStyle = model.fillStyle;
826 this.cc.fill();
827 }
828 if( model.strokeStyle ) {
829 this.cc.strokeStyle = model.strokeStyle;
830 this.cc.stroke();
831 }
832
833 }//for
834 }//for
835
836 this.cc.restore();
837
838 if( this.bgmode == 3 ) {
839 this.drawNightForeground();
840 }
841 };
842
843 function kaiten( x, y, theta2 ) { //数学関数
844 //0,0を原点として回転
845 var theta1 = Math.atan2( y, x );
846 var hankei = Math.sqrt( x * x + y * y );
847 var rx = Math.cos( theta1 + theta2 ) * hankei;
848 var ry = Math.sin( theta1 + theta2 ) * hankei;
849 return { X : rx, Y : ry };
850 }
851 function kaiten2( cx, cy, x, y, theta2 ) { //数学関数
852 //cx,cyを原点として回転
853 x -= cx;
854 y -= cy;
855 var res = kaiten( x, y, theta2 );
856 res.X += cx;
857 res.Y += cy;
858 return res;
859 }
860 function objcopy( obj ) { //オブジェクトを簡易コピーする
861 var res;
862
863 if( obj instanceof Array ) {
864
865 res = new Array();
866 for( var i = 0; i < obj.length; i++ ) {
867 if( typeof obj[ i ] == "object" ) {
868 res[ i ] = objcopy( obj[ i ] );
869 } else {
870 res[ i ] = obj[ i ];
871 }
872 }
873
874 } else if( obj instanceof Object ) {
875
876 res = new Object();
877 for( var name in obj ) {
878 if( typeof obj[ name ] == "object" ) {
879 res[ name ] = objcopy( obj[ name ] );
880 } else {
881 res[ name ] = obj[ name ];
882 }
883 }
884 } else {
885
886 res = null;
887
888 }
889 return res;
890 }
891
892 function getHousen( p ) {
893 /*
894 数学分野の汎用関数
895 法線を得る
896 */
897 var x1 = p[ 0 ].x, y1 = p[ 0 ].y, z1 = p[ 0 ].z;
898 var x2 = p[ 1 ].x, y2 = p[ 1 ].y, z2 = p[ 1 ].z;
899 var x3 = p[ 2 ].x, y3 = p[ 2 ].y, z3 = p[ 2 ].z;
900
901 //法線
902 var res = new Object();
903 res.x = (y2-y1)*(z3-z2)-(z2-z1)*(y3-y2);
904 res.y = (z2-z1)*(x3-x2)-(x2-x1)*(z3-z2);
905 res.z = (x2-x1)*(y3-y2)-(y2-y1)*(x3-x2);
906 res = toNorm( res );
907
908 return res;
909 }
910
911 function toNorm( p ) {
912 /*
913 数学分野の汎用関数
914 原点から座標pまでの距離を1にした座標を返す(単位ベクトル化)
915 */
916 var len = Math.sqrt( p.x * p.x + p.y * p.y + p.z * p.z );
917 var res = new Object();
918 res.x = p.x / len;
919 res.y = p.y / len;
920 res.z = p.z / len;
921 return res;
922 }
923
924 function naiseki( v1, v2 ) {
925 /*
926 数学分野の汎用関数
927 内積を得る
928 */
929 var a = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
930 return a;
931 }