OSDN Git Service

エッジの同期を追加。まだ不完全。
[chnosproject/AI004.git] / mgcanvas / mgcanvas.js
1
2 function MGCanvas(canvasDOMObj){
3         var that = this;
4         //
5         this.initGraphicContext(canvasDOMObj);
6         //
7         this.tickPerSecond = 30;
8         this.tickCount = 0;
9         this.tickTimer = window.setInterval(function(){ that.tick(); }, 1000 / this.tickPerSecond);
10         this.isPaused = false;
11         this.isEnabledAutomaticTracking = true;
12         //
13         this.nodeList = new Array();
14         this.edgeList = new Array();
15         //
16         this.selectedNode = null;
17         this.callback_selectedNodeChanged = null;       //function(newNodeInstance){};
18         //
19         this.selectedNodeDestination = null;
20         this.callback_selectedNodeDestinationChanged = null;    //function(newNodeInstance){};
21         //
22         this.selectedEdge = null;
23         this.callback_selectedEdgeChanged = null;       //function(newEdgeInstance){};
24         //
25         this.srcMemoryDB = null;
26         this.srcMemoryDBSyncPerTick = 60;
27         this.srcMemoryDBSyncCount = this.srcMemoryDBSyncPerTick;
28
29         window.addEventListener('keydown', function(event){
30                 switch(event.keyCode){
31                         case 37:        //左カーソル
32                                 that.moveViewRelative(-10, 0);
33                                 break;
34                         case 39:        //右カーソル
35                                 that.moveViewRelative(10, 0);
36                                 break;
37                         case 38:        //上カーソル
38                                 that.moveViewRelative(0, -10);
39                                 break;
40                         case 40:        //下カーソル
41                                 that.moveViewRelative(0, 10);
42                                 break;
43                 }
44         }, true);
45         
46         this.isMouseDown = false;
47         this.mouseDownPosition = new Point2D(0, 0);
48         this.lastMousePosition = new Point2D(0, 0);
49         this.canvas.onmousemove = function (e){
50                 if(that.isMouseDown){
51                         if(!e){
52                                 //for IE
53                                 e = window.event;
54                         }
55                         that.lastMousePosition = that.getMousePositionOnElement(e);
56                 }
57         };
58         this.canvas.onmousedown = function (e){
59                 if(!e){
60                         //for IE
61                         e = window.event;
62                 }
63                 that.lastMousePosition = that.getMousePositionOnElement(e);
64                 that.mouseDownPosition = that.lastMousePosition;
65                 var p = that.convertPointToGraphLayerFromCanvasLayerP(that.lastMousePosition);
66                 //console.log(p.x + "," + p.y);
67                 var node = that.getNodeAtPointP(p);
68                 that.selectNode(node, e.shiftKey);
69                 var edge = that.getEdgeAtPointP(p);
70                 that.selectEdge(edge);
71                 that.isMouseDown = true;
72                 that.isEnabledAutomaticTracking = false;
73         };
74         this.canvas.onmouseup = function (e){
75                 that.isMouseDown = false;
76         };
77 }
78 MGCanvas.prototype = {
79         setGraph: function(gArray){
80                 //gArray = [[Node1, Node2, Node3, ...], [[Node1, Node3], [Node3, Node2], ...]]
81                 var that = this;
82                 var p = gArray[0];
83                 var n = function(identifier){ return that.nodeList.isIncluded(identifier, function(a, b){ return (a.identifier == b); }); };
84                 
85                 for(var i = 0, iLen = p.length; i < iLen; i++){
86                         this.nodeList.push(new MGNode(this, p[i]));
87                 }
88                 
89                 p = gArray[1];
90                 for(var i = 0, iLen = p.length; i < iLen; i++){
91                         this.edgeList.push(new MGEdge(this, p[i][2], n(p[i][0]), n(p[i][1])));
92                 }
93         },
94         setSourceMemoryDB: function(mdb){
95                 var that = this;
96                 this.srcMemoryDB = mdb;
97                 mdb.callback_updatedNode = function(t){
98                         var n;
99                         n = that.nodeList.isIncluded(t.nodeid, function(a, b){ return a.nodeid == b; });
100                         if(!n){
101                                 // 新規追加
102                                 n = new MGNode(that, t.identifier);
103                                 n.nodeid = t.nodeid;
104                                 n.typeid = t.typeid;
105                                 that.nodeList.push(n);
106                         } else{
107                                 // 更新
108                                 n.identifier = t.identifier;
109                                 n.typeid = t.typeid;
110                         }
111                 };
112                 mdb.callback_updatedEdge = function(t){
113                         var e;
114                         var n = function(nid){ return that.nodeList.isIncluded(nid, function(a, b){ return (a.nodeid == b); }); };
115                         e = that.edgeList.isIncluded(t.edgeid, function(a, b){ return a.edgeid == b; });
116                         if(!e){
117                                 // 新規追加
118                                 e = new MGEdge(that, "", n(t.nodeid0), n(t.nodeid1));
119                                 e.edgeid = t.edgeid;
120                                 e.typeid = t.typeid;
121                                 that.edgeList.push(e);
122                         } else{
123                                 // 更新
124                                 // e.identifier
125                                 e.node0 = n(t.nodeid0);
126                                 e.node1 = n(t.nodeid1);
127                                 e.typeid = t.typeid;
128                         }
129                 };
130         },
131         bringToCenter: function(){
132                 // 重心を求めて、それを表示オフセットに設定する
133                 var g = new Point2D(0, 0);
134                 var p;
135                 p = this.nodeList;
136                 for(var i = 0, iLen = p.length; i < iLen; i++){
137                         g.x += p[i].position.x;
138                         g.y += p[i].position.y;
139                 }
140                 g.x /= p.length;
141                 g.y /= p.length;
142                 
143                 this.positionOffset.x = -g.x;
144                 this.positionOffset.y = -g.y;
145                 
146                 this.isEnabledAutomaticTracking = true;
147         },
148         zoomIn: function(){
149                 this.context.scale(2, 2);
150                 this.currentScale *= 2;
151         },
152         zoomOut: function(){
153                 this.context.scale(0.5, 0.5);
154                 this.currentScale *= 0.5;
155         },
156         moveViewRelative: function(x, y){
157                 this.positionOffset.x += -x;
158                 this.positionOffset.y += -y;
159         },
160         bringInScreen: function(){
161                 //大きく外れていないときには動かさない
162                 var g = new Point2D(0, 0);
163                 var f = new Point2D(0, 0);
164                 var p;
165                 p = this.nodeList;
166                 for(var i = 0, iLen = p.length; i < iLen; i++){
167                         g.x += p[i].position.x;
168                         g.y += p[i].position.y;
169                 }
170                 g.x /= p.length;
171                 g.y /= p.length;
172                 g.x += this.positionOffset.x;
173                 g.y += this.positionOffset.y;
174                 if(     g.x < this.displayRect.origin.x / 2 || 
175                         g.x > -this.displayRect.origin.x / 2 || 
176                         g.y < this.displayRect.origin.y / 2 || 
177                         g.y > -this.displayRect.origin.x / 2){
178                         
179                         this.positionOffset.x += -g.x;
180                         this.positionOffset.y += -g.y;
181                 }
182         },
183         tick: function(){
184                 var p;
185                 var t;
186                 var dr;
187                 var n = null;
188                 var nMax = 0;
189                 var nTemp;
190                 
191                 this.tickCount++;
192                 this.srcMemoryDBSyncCount++;
193                 
194                 //
195                 // Sync
196                 //
197                 if(this.srcMemoryDB && this.srcMemoryDBSyncCount > this.srcMemoryDBSyncPerTick){
198                         this.srcMemoryDB.syncDB();
199                         this.srcMemoryDBSyncCount = 0;
200                 }
201                 
202                 //
203                 // AutomaticTracking
204                 //
205                 if(this.isEnabledAutomaticTracking && (this.tickCount % 30 == 0)){
206                         this.bringInScreen();
207                 }
208
209                 
210                 //
211                 // View moving with mouse
212                 //
213                 if(this.isMouseDown){
214                         this.moveViewRelative(
215                                 (this.mouseDownPosition.x - this.lastMousePosition.x) * 4 / this.tickPerSecond,
216                                 (this.mouseDownPosition.y - this.lastMousePosition.y) * 4 / this.tickPerSecond
217                         );
218                 }
219                 
220                 if(!this.isPaused){
221                         //
222                         // Check
223                         //
224                         /*
225                         p = this.nodeList;
226                         for(var i = 0, iLen = p.length; i < iLen; i++){
227                                 nTemp = this.getVectorLength(p[i].vector);
228                                 if(nMax < nTemp){
229                                         n = p[i];
230                                         nMax = nTemp;
231                                 }
232                         }
233                         if(n){
234                                 n.ignoreEdgeRepulsion = 10;
235                         }
236                         */
237                         
238                         //
239                         // Move
240                         //
241                         p = this.nodeList;
242                         for(var i = 0, iLen = p.length; i < iLen; i++){
243                                 this.nodeList[i].tick();
244                         }
245                         p = this.edgeList;
246                         for(var i = 0, iLen = p.length; i < iLen; i++){
247                                 this.edgeList[i].tick();
248                         }
249                 }
250                 
251                 //
252                 // Refresh
253                 //
254                 dr = this.displayRect;
255                 
256                 this.context.scale(1 / this.currentScale, 1 / this.currentScale);
257                 this.context.clearRect(dr.origin.x, dr.origin.y, dr.size.x, dr.size.y);
258                 this.context.scale(this.currentScale, this.currentScale);
259                 
260                 this.context.translate(this.positionOffset.x, this.positionOffset.y);
261                 
262                 p = this.nodeList;
263                 for(var i = 0, iLen = p.length; i < iLen; i++){
264                         this.nodeList[i].draw();
265                 }
266                 
267                 p = this.edgeList;
268                 for(var i = 0, iLen = p.length; i < iLen; i++){
269                         this.edgeList[i].draw();
270                 }
271                 
272                 this.context.translate(-this.positionOffset.x, -this.positionOffset.y);
273                 
274         },
275         getMousePositionOnElement: function(e){
276                 // http://tmlife.net/programming/javascript/javascript-mouse-pos.html
277                 // retv:
278                 var retv = new Object();
279                 var rect = e.target.getBoundingClientRect();
280                 retv.x = e.clientX - rect.left;
281                 retv.y = e.clientY - rect.top;
282                 return retv;
283         },
284         fillRect: function(x, y, w, h){
285                 var d = this.drawCoordinatesInInteger;
286                 this.context.fillRect(d(x), d(y), d(w), d(h));
287         },
288         strokeRect: function(x, y, w, h){
289                 var d = this.drawCoordinatesInInteger;
290                 this.context.strokeRect(d(x), d(y), d(w), d(h));
291         },
292         drawCoordinatesInInteger: function(coordinateElement){
293                 //from http://www.html5rocks.com/ja/tutorials/canvas/performance/
294                 // With a bitwise or.
295                 return ((0.5 + coordinateElement) | 0);
296         },
297         drawCircle: function(x, y, r){
298                 var d = this.drawCoordinatesInInteger;
299                 this.context.beginPath();
300                 this.context.arc(d(x), d(y), d(r), 0, 2 * Math.PI);
301                 this.context.closePath();
302                 this.context.fill();
303                 this.context.stroke();
304         },
305         drawLineP: function(p, q){
306                 var d = this.drawCoordinatesInInteger;
307                 this.context.beginPath();
308                 this.context.moveTo(d(p.x), d(p.y));
309                 this.context.lineTo(d(q.x), d(q.y));
310                 this.context.closePath();
311                 this.context.stroke();
312         },
313         drawText: function(text, x, y){
314                 //背景をfillStyle
315                 //前景をstrokeStyleで塗りつぶした文字列を描画する
316                 //塗りつぶし高さは20px固定
317                 //座標は文字列の左上の座標となる
318                 var textsize = this.context.measureText(text);
319                 this.context.fillRect(x, y, textsize.width, 20);
320                 this.context.save();
321                 this.context.fillStyle = this.context.strokeStyle;
322                 //fillText引数の座標は文字列の左下!
323                 this.context.fillText(text, x, y + 20 - 1);
324                 this.context.restore();
325         },
326         getVectorLengthP: function(p, q){
327                 return this.getVectorLength(this.getVectorP(p, q));
328         },
329         getVectorLength: function(a){
330                 return Math.sqrt(a.x * a.x + a.y * a.y);
331         },
332         getVectorP: function(p, q){
333                 return new Point2D(q.x - p.x, q.y - p.y);
334         },
335         getUnitVectorP: function(p, q){
336                 var e = this.getVectorP(p, q);
337                 return this.getUnitVector(e);
338         },
339         getUnitVector: function(a){
340                 var l = Math.sqrt(a.x * a.x + a.y * a.y);
341                 a.x /= l;
342                 a.y /= l;
343                 return a;
344         },
345         getNormalUnitVectorSideOfP: function(a, b, p){
346                 //直線ab上にない点pが存在する側へ向かう単位法線ベクトルを返す。
347                 return this.getUnitVector(this.getNormalVectorSideOfP(a, b, p));
348         },
349         getNormalVectorSideOfP: function(a, b, p){
350                 //直線ab上にない点pが存在する側へ向かう法線ベクトルを返す。
351                 //pがab上にある場合は零ベクトルとなる。
352                 var n = this.getVectorP(a, b);
353                 var t = n.x;
354                 var i;
355                 n.x = -n.y;
356                 n.y = t;
357                 
358                 i = this.getInnerVector2D(n, this.getVectorP(a, p));
359                 if(i < 0){
360                         //この法線ベクトルとapの向きが逆なので反転する。
361                         n.x = -n.x;
362                         n.y = -n.y;
363                 } else if(i == 0){
364                         n.x = 0;
365                         n.y = 0;
366                 }
367                 return n;
368         },
369         getExteriorVector2D: function(a, b){
370                 return a.x * b.y - a.y * b.x;
371         },
372         getInnerVector2D: function(a, b){
373                 return a.x * b.x + a.y * b.y;
374         },
375         getDistanceDotAndLineP: function(p, a, b){
376                 // http://www.sousakuba.com/Programming/gs_dot_line_distance.html
377                 var ab;
378                 var ap;
379                 var s;
380                 var l;
381                 var d;
382                 
383                 ab = this.getVectorP(a, b);
384                 ap = this.getVectorP(a, p);
385                 
386                 s = Math.abs(this.getExteriorVector2D(ab, ap));
387                 l = this.getVectorLengthP(a, b);
388                 d = (s / l);
389                 
390                 s = this.getInnerVector2D(ap, ab);
391                 if(s < 0){
392                         //線分の範囲外なので端点aからの距離に変換
393                         //端点から垂線の足までの距離
394                         l = - (s / l);
395                         d = Math.sqrt(d * d + l * l);
396                 } else if(s > l * l){
397                         //同様に端点bからの距離に変換
398                         l = s / l;
399                         d = Math.sqrt(d * d + l * l);
400                 }
401                 return d;
402         },
403         initGraphicContext: function(newCanvas){
404                 this.canvas = newCanvas;
405                 this.context = this.canvas.getContext('2d');
406                 this.context.fillStyle = "rgba(255,255,255,0.5)";
407                 this.context.strokeStyle = "rgba(0, 0, 0, 1)";
408                 this.context.font = "normal 20px sans-serif";
409                 var w = this.canvas.width / 2;
410                 var h = this.canvas.height / 2;
411                 this.context.translate(w, h);
412                 this.displayRect = new Rectangle(-w, -h, this.canvas.width, this.canvas.height);
413                 this.currentScale = 1;
414                 //this.zoomOut();
415                 this.positionOffset = new Point2D(0, 0);
416         },
417         convertPointToGraphLayerFromCanvasLayerP: function(pCanvas){
418                 var p = new Point2D(pCanvas.x, pCanvas.y);
419                 // Canvasの中心が原点
420                 p.x -= this.canvas.width / 2;
421                 p.y -= this.canvas.height / 2;
422                 // スケール変換
423                 p.x /= this.currentScale;
424                 p.y /= this.currentScale;
425                 
426                 // オフセット平行移動
427                 p.x -= this.positionOffset.x;
428                 p.y -= this.positionOffset.y;
429                 
430                 return p;
431         },
432         getNodeAtPointP: function(p){
433                 var r = new Rectangle(p.x - 10, p.y - 10, 20, 20);
434                 
435                 var nl = this.nodeList;
436                 for(var i = 0, iLen = nl.length; i < iLen; i++){
437                         if(r.isIncludePointP(nl[i].position)){
438                                 return nl[i];
439                         }
440                 }
441                 return null;
442         },
443         getEdgeAtPointP: function(p){
444                 var r = new Rectangle(p.x - 10, p.y - 10, 20, 20);
445                 
446                 var el = this.edgeList;
447                 for(var i = 0, iLen = el.length; i < iLen; i++){
448                         if(r.isIncludePointP(el[i].centerPoint)){
449                                 return el[i];
450                         }
451                 }
452                 return null;
453         },
454         selectNode: function(node, destNode){
455                 if(!destNode){
456                         // 通常のノード選択
457                         if(this.selectedNode == node){
458                                 return;
459                         }
460                         if(this.selectedNode){
461                                 this.selectedNode.isSelected = false;
462                         }
463                         if(node){
464                                 node.isSelected = true;
465                         }
466                         this.selectedNode = node;
467                         if(this.callback_selectedNodeChanged){
468                                 this.callback_selectedNodeChanged(this.selectedNode);
469                         }
470                 } else{
471                         // 接続先のノード選択(Shiftキーを押しながら)
472                         if(this.selectedNodeDestination == node){
473                                 return;
474                         }
475                         if(this.selectedNodeDestination){
476                                 this.selectedNodeDestination.isSelected = false;
477                         }
478                         if(node){
479                                 node.isSelected = true;
480                         }
481                         this.selectedNodeDestination = node;
482                         if(this.callback_selectedNodeDestinationChanged){
483                                 this.callback_selectedNodeDestinationChanged(this.selectedNodeDestination);
484                         }
485                 }
486         },
487         selectEdge: function(edge){
488                 if(this.selectedEdge == edge){
489                         return;
490                 }
491                 if(this.selectedEdge){
492                         this.selectedEdge.isSelected = false;
493                 }
494                 if(edge){
495                         edge.isSelected = true;
496                 }
497                 this.selectedEdge = edge;
498                 if(this.callback_selectedEdgeChanged){
499                         this.callback_selectedEdgeChanged(this.selectedEdge);
500                 }
501         },
502         setIdentifierForSelectedNode: function(str, destNode){
503                 if(!destNode){
504                         if(this.selectedNode){
505                                 if(this.srcMemoryDB){
506                                         this.srcMemoryDB.updateNode(str, this.selectedNode.typeid, this.selectedNode.nodeid);
507                                 } else{
508                                         this.selectedNode.identifier = str;
509                                 }
510                         }
511                 } else{
512                         if(this.selectedNodeDestination){
513                                 if(this.srcMemoryDB){
514                                         this.srcMemoryDB.updateNode(str, this.selectedNodeDestination.typeid, this.selectedNodeDestination.nodeid);
515                                 } else{
516                                         this.selectedNodeDestination.identifier = str;
517                                 }
518                         }
519                 }
520         },
521 }
522
523 function MGNode(env, identifier){
524         this.env = env;
525         //
526         this.identifier = identifier;
527         //
528         this.nodeid = undefined;
529         this.typeid = undefined;
530         //
531         this.position = new Point2D(Math.random() * 32 - 16, Math.random() * 32 - 16);
532         this.size = 10;
533         //ランダムな初期ベクトルをもつ。
534         this.vector = new Point2D(Math.random() * 2 - 1, Math.random() * 2 - 1);
535         this.friction = 50 / 100;
536         this.repulsionLengthNode = 90;
537         this.repulsionLengthEdge = 90;
538         this.ignoreEdgeRepulsion = 0;
539         //
540         this.strokeStyle = "rgba(0, 0, 0, 1)";
541         this.isSelected = false;
542 }
543 MGNode.prototype = {
544         draw: function(){
545                 if(this.isSelected){
546                         this.env.context.strokeStyle = "rgba(255, 0, 0, 1)";
547                 } else{
548                         this.env.context.strokeStyle = this.strokeStyle;
549                 }
550                 this.env.drawCircle(this.position.x, this.position.y, this.size);
551                 if(this.identifier){
552                         this.env.drawText(this.identifier.toString(), this.position.x + 10, this.position.y + 10);
553                 }
554         },
555         tick: function(){
556                 var e;
557                 var p;
558                 var l;
559                 var q;
560                 this.position.x += this.vector.x;
561                 this.position.y += this.vector.y;
562                 this.vector.x *= this.friction;
563                 this.vector.y *= this.friction;
564                 
565                 if(!this.ignoreEdgeRepulsion){
566                         //node
567                         //距離の近い点同士には斥力が働くとする。
568                         p = this.env.nodeList;
569                         for(var i = 0, iLen = p.length; i < iLen; i++){
570                                 var q = this.env.nodeList[i];
571                                 if(q != this){
572                                         l = this.env.getVectorLengthP(this.position, q.position);
573                                         if(l < this.repulsionLengthNode && l != 0){
574                                                 e = this.env.getUnitVectorP(q.position, this.position);
575                                                 e.x *= this.repulsionLengthNode / l;
576                                                 e.y *= this.repulsionLengthNode / l;
577                                                 this.vector.x += e.x;
578                                                 this.vector.y += e.y;
579                                         }
580                                 }
581                         }
582                         
583                         //edge
584                         //自分を端点に含まないエッジとの距離が近い場合、そのエッジから遠ざかろうとする。
585                         /*
586                         p = this.env.edgeList;
587                         for(var i = 0, iLen = p.length; i < iLen; i++){
588                                 var q = this.env.edgeList[i];
589                                 if(q.node0 != this && q.node1 != this){
590                                         l = this.env.getDistanceDotAndLineP(this.position, q.node0.position, q.node1.position);
591                                         if(l < this.repulsionLengthEdge && l != 0){
592                                                 if(l < 1){
593                                                         l = 1;
594                                                 }
595                                                 e = this.env.getNormalUnitVectorSideOfP(q.node0.position, q.node1.position, this.position);
596                                                 e.x *= this.repulsionLengthEdge / l;
597                                                 e.y *= this.repulsionLengthEdge / l;
598                                                 this.vector.x += e.x;
599                                                 this.vector.y += e.y;
600                                                 q.node0.vector.x -= e.x / 2;
601                                                 q.node0.vector.y -= e.y / 2;
602                                                 q.node1.vector.x -= e.x / 2;
603                                                 q.node1.vector.y -= e.y / 2;
604                                         }
605                                 }
606                         }
607                         */
608                 } else{
609                         this.ignoreEdgeRepulsion--;
610                 }
611                 
612         },
613 }
614
615 function MGEdge(env, identifier, node0, node1){
616         this.env = env;
617         //
618         this.identifier = identifier;
619         this.node0 = node0;
620         this.node1 = node1;
621         //
622         this.edgeid = null;
623         this.typeid = null;
624         //
625         this.freeLength = 250;
626         //
627         this.strokeStyle = "rgba(0, 0, 0, 0.5)";
628         this.isSelected = false;
629         //
630         this.centerPoint = new Point2D(0, 0);
631 }
632 MGEdge.prototype = {
633         draw: function(){
634                 if(this.isSelected){
635                         this.env.context.strokeStyle = "rgba(255, 0, 0, 1)";
636                 } else{
637                         this.env.context.strokeStyle = this.strokeStyle;
638                 }
639                 if(this.node0 && this.node1){
640                         //this.drawCurvedLineP(this.node0.position, this.node1.position);
641                         this.env.drawLineP(this.node0.position, this.node1.position)
642                         //this.env.strokeRect(this.centerPoint.x - 8, this.centerPoint.y - 8, 16, 16);
643                         if(this.identifier){
644                                 this.env.drawText(this.identifier.toString(), this.centerPoint.x, this.centerPoint.y);
645                         }
646                 }
647         },
648         tick: function(){
649                 var l = this.env.getVectorLengthP(this.node0.position, this.node1.position);
650                 var e;
651                 if(l > this.freeLength){
652                         e = this.env.getUnitVectorP(this.node0.position, this.node1.position);
653                         e.x *= l / this.freeLength;
654                         e.y *= l / this.freeLength;
655                         this.node0.vector.x += e.x;
656                         this.node0.vector.y += e.y;
657                         this.node1.vector.x -= e.x;
658                         this.node1.vector.y -= e.y;
659                 }
660                 this.centerPoint = new Point2D((this.node0.position.x + this.node1.position.x) / 2, (this.node0.position.y + this.node1.position.y) / 2);
661         },
662         drawCurvedLineP: function(p, q){
663                 var that = this;
664                 var d = function(x){ return that.env.drawCoordinatesInInteger(x); };
665                 var v = this.env.getUnitVectorP(p, q);
666                 var w = new Point2D(-(v.y * 50), v.x * 50);
667                 this.env.context.beginPath();
668                 this.env.context.moveTo(d(p.x), d(p.y));
669                 this.env.context.bezierCurveTo(d(this.centerPoint.x + w.x), d(this.centerPoint.y + w.y), d(this.centerPoint.x - w.x), d(this.centerPoint.y - w.y), d(q.x), d(q.y));
670                 //this.env.context.closePath();
671                 this.env.context.stroke();
672         },
673 }
674
675 function Point2D(x, y){
676         this.x = x;
677         this.y = y;
678 }
679 Point2D.prototype = {
680         
681 }
682
683 function Rectangle(x, y, width, height){
684         this.origin = new Point2D(x,y);
685         this.size = new Point2D(width,height);
686 }
687 Rectangle.prototype = {
688         isIncludePointP: function(p){
689                 return  (this.origin.x <= p.x) && (p.x <= this.origin.x + this.size.x) &&
690                                 (this.origin.y <= p.y) && (p.y <= this.origin.y + this.size.y);
691         },
692 }
693