PageSourceSearch

https://thevirtualheart.org/assets/js/collie.js

js thevirtualheart.org collected 2026-10-02 04:25:17 UTC 277,265 bytes, 9,552 lines download raw bytes

1/**
2 * @namespace
3 */
4var collie = collie || {};
5
6(function () {
7	/**
8	 * 콜리 버전
9	 * 
10	 * @name collie.version
11	 * @description 자동 치환되므로 직접 수정하지 않는다.
12	 */
13	collie.version = "1.1.0";
14	
15	/**
16	 * 클래스 만들기
17	 * 
18	 * @method collie#Class
19	 * @param {Object} o 클래스 멤버, $init을 이용해 생성자를 정의할 수 있다.
20	 * @param {collie.Class} oParent 상속받을 부모 클래스
21	 * @return {collie.Class}
22	 * @example
23	 * var Person = collie.Class({
24	 *  gender : false,
25	 * 	walk : function () { return "walking!"; }
26	 * });
27	 * 
28	 * var Male = collie.Class({
29	 * 	name : "",
30	 *  gender : "male"
31	 * }, Person);
32	 * 
33	 * var oDavid = new Male();
34	 * oDavid.name = "david";
35	 * 
36	 * alert(oDavid.name); // david
37	 * alert(oDavid.gender); // male
38	 * alert(oDavid.walk()); // walking!
39	 * 
40	 * @example
41	 * override
42	 * var Person = collie.Class({
43	 * 	testMethod : function () {
44	 * 	
45	 * 	}
46	 * });
47	 * 
48	 * var Male = collie.Class({
49	 * 	testMethod : function () {
50	 * 		// blah
51	 * 		this.constructor.$super.testMethod.apply(this, arguments);		
52	 * 	}
53	 * }, Person);
54	 */
55	collie.Class = function (o, oParent) {		
56		var $init = null;
57		
58		if ("$init" in o) {
59			$init = o.$init;
60			delete o.$init;
61		}
62		
63		var F = function () {
64			if ($init !== null) {
65				$init.apply(this, arguments);
66			}
67		};
68		
69		if (typeof oParent !== "undefined") {
70			F = function () {
71				// 부모의 생성자 실행
72				oParent.apply(this, arguments);
73				
74				// 자식의 생성자 실행
75				if ($init !== null) {
76					$init.apply(this, arguments);
77				}
78			};
79			
80			var Parent = function () {};
81			Parent.prototype = oParent.prototype;
82			F.$super = oParent.prototype;
83			F.prototype = new Parent();
84			F.prototype.constructor = F;
85		}
86		
87		for (var i in o) {
88			if (o.hasOwnProperty(i) && i !== "prototype") {
89				F.prototype[i] = o[i];
90			}
91		}
92		
93		return F;
94	};
95	
96	/**
97	 * 자주 쓰이는 유틸 모음
98	 * @namespace
99	 */
100	collie.util = new (collie.Class(/** @lends collie.util */{
101		$init : function () {
102			this._sCSSPrefix = null;
103			this._htDeviceInfo = null;
104			this._bSupport3d = null;
105			this._bSupportCSS3 = null;
106			this._htBoundary = {
107				left : 0,
108				right : 0,
109				top : 0,
110				bottom : 0
111			};
112		},
113		
114		/**
115		 * 아이디로 표시 객체 인스턴스를 가져온다
116		 * 주로 DOM 방식일 때 사용 된다
117		 * 
118		 * @param {Number} nId
119		 * @return {collie.DisplayObject}
120		 */
121		getDisplayObjectById : function (nId) {
122			return collie.DisplayObject.htFactory[nId];
123		},
124		
125		/**
126		 * name으로 표시 객체 인스턴스를 가져온다
127		 * 
128		 * @param {String} sName
129		 * @return {collie.DisplayObject}
130		 */
131		getDisplayObjectByName : function (sName) {
132			for (var i in collie.DisplayObject.htFactory) {
133				if (collie.DisplayObject.htFactory[i].get("name") === sName) {
134					return collie.DisplayObject.htFactory[i];
135				}
136			}
137			
138			return false;
139		},
140		
141		/**
142		 * userAgent 값으로 현재 단말 정보를 반환 한다
143		 * 값을 한번 얻어오면 다음부터는 캐시된 값을 사용 한다
144		 * 
145		 * @return {Object} htInfo
146		 * @return {Boolean} htInfo.desktop 데스크탑 여부
147		 * @return {Boolean} htInfo.supportCanvas 캔버스 지원 여부
148		 * @return {Boolean|Number} htInfo.android 안드로이드라면 두번째까지의 버젼, 안드로이드가 아니라면 false
149		 * @return {Boolean|Number} htInfo.ios iOS라면 두번째까지의 버젼, iOS가 아니라면 false
150		 * @return {Boolean|Number} htInfo.ie IE 브라우저라면 첫번째까지의 버전, IE 브라우저가 아니라면 false
151		 * @return {Boolean|Number} htInfo.chrome Agent에 Chrome이 포함돼 있는지 여부
152		 */
153		getDeviceInfo : function (sAgent) {
154			if (this._htDeviceInfo !== null && typeof sAgent === "undefined") {
155				return this._htDeviceInfo;
156			}
157			
158			var aMat = null;
159			var bIsDesktop = false;
160			var bSupportCanvas = typeof CanvasRenderingContext2D !== "undefined";
161			var bIsAndroid = false;
162			var bIsIOS = false;
163			var bIsIE = false;
164			var bHasChrome = (/chrome/i.test(sAgent)) ? true : false;
165			var sAgent = sAgent || navigator.userAgent;
166			var nVersion = 0;
167			
168			if (/android/i.test(sAgent)) { // android
169				bIsAndroid = true;
170				aMat = sAgent.toString().match(/android ([0-9]\.[0-9])\.?([0-9]?)/i);
171				
172				if (aMat && aMat[1]) {
173					nVersion = (parseFloat(aMat[1]) + (aMat[2] ? aMat[2] * 0.01 : 0)).toFixed(2);
174				}
175			} else if (/(iphone|ipad|ipod)/i.test(sAgent)) { // iOS
176				bIsIOS = true;
177				aMat = sAgent.toString().match(/([0-9]_[0-9])/i);
178				
179				if (aMat && aMat[1]) {
180					nVersion = parseFloat(aMat[1].replace(/_/, '.'));
181				}
182			} else { // PC
183				bIsDesktop = true;
184				
185				if (/(MSIE)/i.test(sAgent)) { // IE
186					bIsIE = true;
187					aMat = sAgent.toString().match(/MSIE ([0-9])/i);
188					
189					if (aMat && aMat[1]) {
190						nVersion = parseInt(aMat[1], 10);
191					}
192				}
193			}
194			
195			this._htDeviceInfo = {
196				supportCanvas : bSupportCanvas,
197				desktop : bIsDesktop,
198				android : bIsAndroid ? nVersion : false,
199				ios : bIsIOS ? nVersion : false,
200				ie : bIsIE ? nVersion : false,
201				chrome : bHasChrome
202			};
203			
204			return this._htDeviceInfo;
205		},
206		
207		/**
208		 * 브라우저에 따른 CSS Prefix를 반환
209		 * 
210		 * @param {String} sName 대상 CSS 속성 명 (- 포함), 값이 없으면 prefix만 반환
211		 * @param {Boolean} bJavascript 자바스크립트 속성 타입으로 반환
212		 * @example
213		 * collie.util.getCSSPrefix("transform"); // -webkit-transform
214		 * collie.util.getCSSPrefix("transform", true); // webkitTransform
215		 * 
216		 * // prefix가 없을 때
217		 * collie.util.getCSSPrefix("transform"); // transform
218		 * collie.util.getCSSPrefix("transform", true); // transform
219		 * @return {String} 조합된 CSS Prefix, 혹은 속성 명
220		 */
221		getCSSPrefix : function (sName, bJavascript) {
222			var sResult = '';
223			
224			if (this._sCSSPrefix === null) {
225				this._sCSSPrefix = '';
226				
227				// webkit이 가장 먼저 쓰일 것 같아서 webkit을 최상단으로 옮김
228				if (typeof document.body.style.webkitTransform !== "undefined") {
229					this._sCSSPrefix = "-webkit-";
230				} else if (typeof document.body.style.MozTransform !== "undefined") {
231					this._sCSSPrefix = "-moz-";
232				} else if (typeof document.body.style.OTransform !== "undefined") {
233					this._sCSSPrefix = "-o-";
234				} else if (typeof document.body.style.msTransform !== "undefined") {
235					this._sCSSPrefix = "-ms-";
236				}
237			}
238			
239			sResult = this._sCSSPrefix + (sName ? sName : '');
240			
241			// - 빼기
242			if (bJavascript) {
243				var aTmp = sResult.split("-");
244				sResult = '';
245				
246				for (var i = 0, len = aTmp.length; i < len; i++) {
247					if (aTmp[i]) {
248						sResult += sResult ? aTmp[i].substr(0, 1).toUpperCase() + aTmp[i].substr(1) : aTmp[i];
249					}
250				}
251				
252				if (this._sCSSPrefix === "-moz-" || this._sCSSPrefix === "-o-") {
253					sResult = sResult.substr(0, 1).toUpperCase() + sResult.substr(1);
254				}
255			}
256			
257			return sResult;
258		},
259		
260		/**
261		 * CSS3를 지원하는지 여부
262		 * 
263		 * @return {Boolean}
264		 */
265		getSupportCSS3 : function () {
266			if (this._bSupportCSS3 === null) {
267				this._bSupportCSS3 = typeof document.body.style[collie.util.getCSSPrefix("transform", true)] !== "undefined" || typeof document.body.style.transform != "undefined";
268			}
269			
270			return this._bSupportCSS3;
271		},
272		
273		/**
274		 * CSS3d를 지원하는지 여부
275		 * 
276		 * @return {Boolean}
277		 */
278		getSupportCSS3d : function () {
279			if (this._bSupport3d === null) {
280				this._bSupport3d = (typeof document.body.style[collie.util.getCSSPrefix("perspective", true)] !== "undefined" || typeof document.body.style.perspective != "undefined") && (!collie.util.getDeviceInfo().android || collie.util.getDeviceInfo().android >= 4);
281			}
282			
283			return this._bSupport3d;
284		},
285		
286		/**
287		 * 각도를 라디안으로 변환
288		 * 
289		 * @param {Number} nDeg
290		 * @return {Number}
291		 */
292		toRad : function (nDeg) {
293			return nDeg * Math.PI / 180;
294		},
295		
296		/**
297		 * 라디안을 각도로 변환
298		 * 
299		 * @param {Number} nRad
300		 * @return {Number}
301		 */
302		toDeg : function (nRad) {
303			return nRad * 180 / Math.PI;
304		},
305		
306		/**
307		 * 근사값 구함(소수 7자리 미만은 버림)
308		 * - javascript 소숫점 연산 오류로 인한 근사값 연산임
309		 * 
310		 * @param {Number} nValue 값
311		 * @return {Number}
312		 */
313		approximateValue : function (nValue) {
314			return Math.round(nValue * 10000000) / 10000000;
315		},
316		
317		/**
318		 * 각도를 0~360 값 사이로 맞춤
319		 * 
320		 * @param {Number} nAngleRad 라디안 값
321		 * @return {Number}
322		 */
323		fixAngle : function (nAngleRad) {
324			var nAngleDeg = collie.util.toDeg(nAngleRad);
325			nAngleDeg -= Math.floor(nAngleDeg / 360) * 360;
326			return collie.util.toRad(nAngleDeg);
327		},
328		
329		/**
330		 * 거리를 반환
331		 * 
332		 * @param {Number} x1
333		 * @param {Number} y1
334		 * @param {Number} x2
335		 * @param {Number} y2
336		 * @return {Number} 거리
337		 */
338		getDistance : function (x1, y1, x2, y2) {
339			return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
340		},
341		
342		/**
343		 * 점 배열에서 최소 사각형 영역을 구한다
344		 * 
345		 * @param {Array} aPoints 대상 배열 [[x1, y1], [x2, y2], ... ]
346		 * @return {Object} htResult
347		 * @return {Number} htResult.left
348		 * @return {Number} htResult.right
349		 * @return {Number} htResult.bottom
350		 * @return {Number} htResult.top
351		 */
352		getBoundary : function (aPoints) {
353			var nMinX = aPoints[0][0];
354			var nMaxX = aPoints[0][0];
355			var nMinY = aPoints[0][1];
356			var nMaxY = aPoints[0][1];
357			
358			for (var i = 1, len = aPoints.length; i < len; i++) {
359				nMinX = Math.min(nMinX, aPoints[i][0]);
360				nMaxX = Math.max(nMaxX, aPoints[i][0]);
361				nMinY = Math.min(nMinY, aPoints[i][1]);
362				nMaxY = Math.max(nMaxY, aPoints[i][1]);
363			}
364			
365			return {
366				left : nMinX,
367				right : nMaxX,
368				top : nMinY,
369				bottom : nMaxY
370			};
371		},
372		
373		/**
374		 * boundary를 points로 변환한다
375		 * 
376		 * @param {Object} htBoundary
377		 * @param {Number} htBoundary.left
378		 * @param {Number} htBoundary.right
379		 * @param {Number} htBoundary.top
380		 * @param {Number} htBoundary.bottom
381		 * @return {Array} points [[left, top], [right, top], [right, bottom], [left, bottom]]
382		 */
383		getBoundaryToPoints : function (htBoundary) {
384			return [[htBoundary.left, htBoundary.top], [htBoundary.right, htBoundary.top], [htBoundary.right, htBoundary.bottom], [htBoundary.left, htBoundary.bottom]];
385		},
386		
387		/**
388		 * 주소의 queryString을 객체화 한다
389		 * @return {Object}
390		 */
391		queryString : function () {
392			var htResult = {};
393			
394			if (location.search) {
395				var aParam = location.search.substr(1).split("&");
396				
397				for (var i = 0, len = aParam.length; i < len; i++) {
398					var aKeyValue = aParam[i].split("=");
399					htResult[aKeyValue.shift()] = aKeyValue.join("=");
400				}
401			}
402			
403			return htResult;
404		},
405		
406		/**
407		 * 객체를 복사
408		 * 
409		 * @param {Object} oSource 원본 객체
410		 * @return {Object}
411		 */
412		cloneObject : function (oSource) {
413			var oReturn = {};
414			
415			for (var i in oSource) {
416				oReturn[i] = oSource[i];
417			}
418			
419			return oReturn;
420		},
421		
422		/**
423		 * zIndex에 따라 오름차순 정렬된 순서로 배열에 넣는다
424		 * 
425		 * @private
426		 * @param {Array} aTarget
427		 * @param {collie.DisplayObject} oDisplayObject
428		 */
429		pushWithSort : function (aTarget, oDisplayObject) {
430			var bAdded = false;
431			
432			for (var i = 0, len = aTarget.length; i < len; i++) {
433				if (aTarget[i]._htOption.zIndex > oDisplayObject._htOption.zIndex) {
434					aTarget.splice(i, 0, oDisplayObject);
435					bAdded = true;
436					break;
437				}
438			}
439			
440			if (!bAdded) {
441				aTarget.push(oDisplayObject);
442			}
443		},
444		
445		/**
446		 * DOM의 addEventListener
447		 * 
448		 * @param {HTMLElement} el
449		 * @param {String} sName 이벤트 이름, on을 제외한 이름
450		 * @param {Function} fHandler 바인딩할 함수
451		 * @param {Boolean} bUseCapture 캡쳐 사용 여부
452		 */
453		addEventListener : function (el, sName, fHandler, bUseCapture) {
454			if ("addEventListener" in el) {
455				el.addEventListener(sName, fHandler, bUseCapture);
456			} else {
457				el.attachEvent("on" + sName, fHandler, bUseCapture);
458			}
459		},
460		
461		/**
462		 * DOM의 removeEventListener
463		 * 
464		 * @param {HTMLElement} el
465		 * @param {String} sName 이벤트 이름, on을 제외한 이름
466		 * @param {Function} fHandler 바인딩할 함수
467		 * @param {Boolean} bUseCapture 캡쳐 사용 여부
468		 */
469		removeEventListener : function (el, sName, fHandler, bUseCapture) {
470			if ("removeEventListener" in el) {
471				el.removeEventListener(sName, fHandler, bUseCapture);
472			} else {
473				el.detachEvent("on" + sName, fHandler, bUseCapture);
474			}
475		},
476		
477		/**
478		 * 이벤트의 기본 동작을 멈춘다
479		 * 
480		 * @param {HTMLEvent} e
481		 */
482		stopEventDefault : function (e) {
483			e = e || window.event;
484			
485			if ("preventDefault" in e) {
486				e.preventDefault();
487			}
488			
489			e.returnValue = false;
490		},
491		
492		/**
493		 * 엘리먼트의 위치를 구한다
494		 * 
495		 * @param {HTMLElement}
496		 * @return {Object} htResult
497		 * @return {Number} htResult.x 
498		 * @return {Number} htResult.y
499		 * @return {Number} htResult.width
500		 * @return {Number} htResult.height
501		 */
502		getPosition : function (el) {
503			var elDocument = el.ownerDocument || el.document || document;
504			var elHtml = elDocument.documentElement;
505			var elBody = elDocument.body;
506			var htPosition = {};
507			
508			if ("getBoundingClientRect" in el) {
509				var htBox = el.getBoundingClientRect();
510				htPosition.x = htBox.left;
511				htPosition.x += elHtml.scrollLeft || elBody.scrollLeft;
512				htPosition.y = htBox.top;
513				htPosition.y += elHtml.scrollTop || elBody.scrollTop;
514				htPosition.width = htBox.width;
515				htPosition.height = htBox.height;
516			} else {
517				htPosition.x = 0;
518				htPosition.y = 0;
519				htPosition.width = el.offsetWidth;
520				htPosition.height = el.offsetHeight;
521				
522				for (var o = el; o; o = o.offsetParent) {
523					htPosition.x += o.offsetLeft;
524					htPosition.y += o.offsetTop;
525				}
526	
527				for (var o = el.parentNode; o; o = o.parentNode) {
528					if (o.tagName === 'BODY') {
529						break;
530					}
531					
532					if (o.tagName === 'TR') {
533						htPosition.y += 2;
534					}
535										
536					htPosition.x -= o.scrollLeft;
537					htPosition.y -= o.scrollTop;
538				}
539			}
540			
541			return htPosition;
542		}
543	}))();
544	
545	// iOS에서 상단바 숨기기
546	if (collie.util.getDeviceInfo().ios) {
547		window.addEventListener("load", function () {
548			setTimeout(function () {
549				document.body.scrollTop = 0;
550			}, 300);
551		});
552	}
553	
554	// bind polyfill, https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
555	if (!Function.prototype.bind) {
556		Function.prototype.bind = function (oThis) {
557			if (typeof this !== "function") {
558				// closest thing possible to the ECMAScript 5 internal IsCallable function
559				throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
560			}
561	
562			var aArgs = Array.prototype.slice.call(arguments, 1), 
563				fToBind = this, 
564				fNOP = function () {},
565				fBound = function () {
566					return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
567				};
568	
569			fNOP.prototype = this.prototype;
570			fBound.prototype = new fNOP();
571			return fBound;
572		};
573	}
574	
575	// Implementation the dashedLine method in Canvas
576	// I had fix some difference with Raphael.js
577	// special thanks to Phrogz and Rod MacDougall
578	// http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
579	var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
580	if (CP && CP.lineTo) {
581		CP._dashedLineProperty = {
582			index : 0,
583			length : 0
584		};
585		
586		CP.resetDashedLine = function () {
587			this._dashedLineProperty = {
588				index : 0,
589				length : 0
590			};
591		};
592		
593	    CP.dashedLine = function(x, y, x2, y2, da, width) {
594	        if (!da) da = [10, 5];
595	        var dx = (x2-x), dy = (y2-y);
596	        var len = Math.sqrt(dx * dx + dy * dy);
597	        var rot = Math.atan2(dy, dx);
598	        var dc = da.length;
599	        var di = this._dashedLineProperty.index || 0;
600	        var cx = this._dashedLineProperty.length || 0;
601	        var cy = 0;
602	        var sx = 0;
603	        var sy = 0;
604	        
605	        while (len > cx) {
606	        	if (sx !== 0 || cx === 0) {
607		            cx += da[di++ % dc] * width;
608	        	}
609	            
610	            if (cx > len) {
611	        		this._dashedLineProperty.length = cx - len;
612	            	cx = len;
613	            }
614	            
615	            sx = x + cx * Math.cos(rot);
616	            sy = y + cx * Math.sin(rot);
617	            di % 2 === 1 ? this.lineTo(sx, sy) : this.moveTo(sx, sy);
618	        }
619	        
620	        this._dashedLineProperty.index = di;
621	    }
622	}
623	
624	collie.raphaelDashArray = {
625        "": [0],
626        "none": [0],
627        "-": [3, 1],
628        ".": [1, 1],
629        "-.": [3, 1, 1, 1],
630        "-..": [3, 1, 1, 1, 1, 1],
631        ". ": [1, 3],
632        "- ": [4, 3],
633        "--": [8, 3],
634        "- .": [4, 3, 1, 3],
635        "--.": [8, 3, 1, 3],
636        "--..": [8, 3, 1, 3, 1, 3]
637	};
638})();
639/*
640 * TERMS OF USE - EASING EQUATIONS
641 * Open source under the BSD License.
642 * Copyright (c) 2001 Robert Penner, all rights reserved.
643 */
644/**
645 * 새로운 이펙트 함수를 생성한다.
646 * 진도 프레임워크의 jindo.Effect를 사용
647 * @namespace 수치의 중간값을 쉽게 얻을 수 있게 하는 static 컴포넌트
648 * @function
649 * @param {Function} fEffect 0~1 사이의 숫자를 인자로 받아 정해진 공식에 따라 0~1 사이의 값을 리턴하는 함수
650 * @return {Function} 이펙트 함수. 이 함수는 시작값과 종료값을 입력하여 특정 시점에 해당하는 값을 구하는 타이밍 함수를 생성한다.
651 */
652collie.Effect = function(fEffect) {
653	if (this instanceof arguments.callee) {
654		throw new Error("You can't create a instance of this");
655	}
656	
657	var rxNumber = /^(\-?[0-9\.]+)(%|px|pt|em)?$/,
658		rxRGB = /^rgb\(([0-9]+)\s?,\s?([0-9]+)\s?,\s?([0-9]+)\)$/i,
659		rxHex = /^#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
660		rx3to6 = /^#([0-9A-F])([0-9A-F])([0-9A-F])$/i;
661	
662	var getUnitAndValue = function(v) {
663		var nValue = v, sUnit;
664		
665		if (rxNumber.test(v)) {
666			nValue = parseFloat(v); 
667			sUnit = RegExp.$2 || "";
668		} else if (rxRGB.test(v)) {
669			nValue = [parseInt(RegExp.$1, 10), parseInt(RegExp.$2, 10), parseInt(RegExp.$3, 10)];
670			sUnit = 'color';
671		} else if (rxHex.test(v = v.replace(rx3to6, '#$1$1$2$2$3$3'))) {
672			nValue = [parseInt(RegExp.$1, 16), parseInt(RegExp.$2, 16), parseInt(RegExp.$3, 16)];
673			sUnit = 'color';
674		} 
675				
676		return { 
677			nValue : nValue, 
678			sUnit : sUnit 
679		};
680	};
681	
682	return function(nStart, nEnd) {
683		var sUnit;
684		if (arguments.length > 1) {
685			nStart = getUnitAndValue(nStart);
686			nEnd = getUnitAndValue(nEnd);
687			sUnit = nEnd.sUnit;
688		} else {
689			nEnd = getUnitAndValue(nStart);
690			nStart = null;
691			sUnit = nEnd.sUnit;
692		} 
693		
694		// 두개의 단위가 다르면
695		if (nStart && nEnd && nStart.sUnit != nEnd.sUnit) {
696			throw new Error('unit error');
697		}
698		
699		nStart = nStart && nStart.nValue;
700		nEnd = nEnd && nEnd.nValue;
701		
702		var fReturn = function(p) {
703			var nValue = fEffect(p),
704				getResult = function(s, d) {
705					return (d - s) * nValue + s + sUnit; 
706				};
707			
708			if (sUnit == 'color') {
709				var r = Math.max(0, Math.min(255, parseInt(getResult(nStart[0], nEnd[0]), 10))) << 16;
710				r |= Math.max(0, Math.min(255, parseInt(getResult(nStart[1], nEnd[1]), 10))) << 8;
711				r |= Math.max(0, Math.min(255, parseInt(getResult(nStart[2], nEnd[2]), 10)));
712				
713				r = r.toString(16).toUpperCase();
714				for (var i = 0; 6 - r.length; i++) {
715					r = '0' + r;
716				}
717					
718				return '#' + r;
719			}
720			return getResult(nStart, nEnd);
721		};
722		
723		if (nStart === null) {
724			fReturn.setStart = function(s) {
725				s = getUnitAndValue(s);
726				
727				if (s.sUnit != sUnit) {
728					throw new Error('unit eror');
729				}
730				nStart = s.nValue;
731			};
732		}
733		return fReturn;
734	};
735};
736
737/**
738 * linear 이펙트 함수
739 */
740collie.Effect.linear = collie.Effect(function(s) {
741	return s;
742});
743
744/**
745 * easeInSine 이펙트 함수
746 */
747collie.Effect.easeInSine = collie.Effect(function(s) {
748	return (s == 1) ? 1 : -Math.cos(s * (Math.PI / 2)) + 1;
749});
750/**
751 * easeOutSine 이펙트 함수
752 */
753collie.Effect.easeOutSine = collie.Effect(function(s) {
754	return Math.sin(s * (Math.PI / 2));
755});
756/**
757 * easeInOutSine 이펙트 함수
758 */
759collie.Effect.easeInOutSine = collie.Effect(function(s) {
760	return (s < 0.5) ? collie.Effect.easeInSine(0, 1)(2 * s) * 0.5 : collie.Effect.easeOutSine(0, 1)((2 * s) - 1) * 0.5 + 0.5;
761});
762/**
763 * easeOutInSine 이펙트 함수
764 */
765collie.Effect.easeOutInSine = collie.Effect(function(s) {
766	return (s < 0.5) ? collie.Effect.easeOutSine(0, 1)(2 * s) * 0.5 : collie.Effect.easeInSine(0, 1)((2 * s) - 1) * 0.5 + 0.5;
767});
768
769/**
770 * easeInQuad 이펙트 함수
771 */
772collie.Effect.easeInQuad = collie.Effect(function(s) {
773	return s * s;
774});
775/**
776 * easeOutQuad 이펙트 함수
777 */
778collie.Effect.easeOutQuad = collie.Effect(function(s) {
779	return -(s * (s - 2));
780});
781/**
782 * easeInOutQuad 이펙트 함수
783 */
784collie.Effect.easeInOutQuad = collie.Effect(function(s) {
785	return (s < 0.5) ? collie.Effect.easeInQuad(0, 1)(2 * s) * 0.5 : collie.Effect.easeOutQuad(0, 1)((2 * s) - 1) * 0.5 + 0.5;
786});
787/**
788 * easeOutInQuad 이펙트 함수
789 */
790collie.Effect.easeOutInQuad = collie.Effect(function(s) {
791	return (s < 0.5) ? collie.Effect.easeOutQuad(0, 1)(2 * s) * 0.5 : collie.Effect.easeInQuad(0, 1)((2 * s) - 1) * 0.5 + 0.5;
792});
793
794/**
795 * easeInCubic 이펙트 함수
796 */
797collie.Effect.easeInCubic = collie.Effect(function(s) {
798	return Math.pow(s, 3);
799});
800/**
801 * easeOutCubic 이펙트 함수
802 */
803collie.Effect.easeOutCubic = collie.Effect(function(s) {
804	return Math.pow((s - 1), 3) + 1;
805});
806/**
807 * easeInOutCubic 이펙트 함수
808 */
809collie.Effect.easeInOutCubic = collie.Effect(function(s) {
810	return (s < 0.5) ? collie.Effect.easeIn(0, 1)(2 * s) * 0.5 : collie.Effect.easeOut(0, 1)((2 * s) - 1) * 0.5 + 0.5;
811});
812/**
813 * easeOutInCubic 이펙트 함수
814 */
815collie.Effect.easeOutInCubic = collie.Effect(function(s) {
816	return (s < 0.5) ? collie.Effect.easeOut(0, 1)(2 * s) * 0.5 : collie.Effect.easeIn(0, 1)((2 * s) - 1) * 0.5 + 0.5;
817});
818
819/**
820 * easeInQuart 이펙트 함수
821 */
822collie.Effect.easeInQuart = collie.Effect(function(s) {
823	return Math.pow(s, 4);
824});
825/**
826 * easeOutQuart 이펙트 함수
827 */
828collie.Effect.easeOutQuart = collie.Effect(function(s) {
829	return -(Math.pow(s - 1, 4) - 1);
830});
831/**
832 * easeInOutQuart 이펙트 함수
833 */
834collie.Effect.easeInOutQuart = collie.Effect(function(s) {
835	return (s < 0.5) ? collie.Effect.easeInQuart(0, 1)(2 * s) * 0.5 : collie.Effect.easeOutQuart(0, 1)((2 * s) - 1) * 0.5 + 0.5;
836});
837/**
838 * easeOutInQuart 이펙트 함수
839 */
840collie.Effect.easeOutInQuart = collie.Effect(function(s) {
841	return (s < 0.5) ? collie.Effect.easeOutQuart(0, 1)(2 * s) * 0.5 : collie.Effect.easeInQuart(0, 1)((2 * s) - 1) * 0.5 + 0.5;
842});
843
844/**
845 * easeInQuint 이펙트 함수
846 */
847collie.Effect.easeInQuint = collie.Effect(function(s) {
848	return Math.pow(s, 5);
849});
850/**
851 * easeOutQuint 이펙트 함수
852 */
853collie.Effect.easeOutQuint = collie.Effect(function(s) {
854	return Math.pow(s - 1, 5) + 1;
855});
856/**
857 * easeInOutQuint 이펙트 함수
858 */
859collie.Effect.easeInOutQuint = collie.Effect(function(s) {
860	return (s < 0.5) ? collie.Effect.easeInQuint(0, 1)(2 * s) * 0.5 : collie.Effect.easeOutQuint(0, 1)((2 * s) - 1) * 0.5 + 0.5;
861});
862/**
863 * easeOutInQuint 이펙트 함수
864 */
865collie.Effect.easeOutInQuint = collie.Effect(function(s) {
866	return (s < 0.5) ? collie.Effect.easeOutQuint(0, 1)(2 * s) * 0.5 : collie.Effect.easeInQuint(0, 1)((2 * s) - 1) * 0.5 + 0.5;
867});
868
869/**
870 * easeInCircle 이펙트 함수
871 */
872collie.Effect.easeInCircle = collie.Effect(function(s) {
873	return -(Math.sqrt(1 - (s * s)) - 1);
874});
875/**
876 * easeOutCircle 이펙트 함수
877 */
878collie.Effect.easeOutCircle = collie.Effect(function(s) {
879	return Math.sqrt(1 - (s - 1) * (s - 1));
880});
881/**
882 * easeInOutCircle 이펙트 함수
883 */
884collie.Effect.easeInOutCircle = collie.Effect(function(s) {
885	return (s < 0.5) ? collie.Effect.easeInCircle(0, 1)(2 * s) * 0.5 : collie.Effect.easeOutCircle(0, 1)((2 * s) - 1) * 0.5 + 0.5;
886});
887/**
888 * easeOutInCircle 이펙트 함수
889 */
890collie.Effect.easeOutInCircle = collie.Effect(function(s) {
891	return (s < 0.5) ? collie.Effect.easeOutCircle(0, 1)(2 * s) * 0.5 : collie.Effect.easeInCircle(0, 1)((2 * s) - 1) * 0.5 + 0.5;
892});
893
894/**
895 * easeInBack 이펙트 함수
896 */
897collie.Effect.easeInBack = collie.Effect(function(s) {
898	var n = 1.70158;
899	return (s == 1) ? 1 : (s / 1) * (s / 1) * ((1 + n) * s - n);
900});
901/**
902 * easeOutBack 이펙트 함수
903 */
904collie.Effect.easeOutBack = collie.Effect(function(s) {
905	var n = 1.70158;
906	return (s === 0) ? 0 : (s = s / 1 - 1) * s * ((n + 1) * s + n) + 1;
907});
908/**
909 * easeInOutBack 이펙트 함수
910 */
911collie.Effect.easeInOutBack = collie.Effect(function(s) {
912	return (s < 0.5) ? collie.Effect.easeInBack(0, 1)(2 * s) * 0.5 : collie.Effect.easeOutBack(0, 1)((2 * s) - 1) * 0.5 + 0.5;
913});
914
915/**
916 * easeInElastic 이펙트 함수
917 */
918collie.Effect.easeInElastic = collie.Effect(function(s) {
919	var p = 0, a = 0, n;
920	if (s === 0) {
921		return 0;
922	}
923	if ((s/=1) == 1) {
924		return 1;
925	}
926	if (!p) {
927		p = 0.3;
928	}
929	if (!a || a < 1) { 
930		a = 1; n = p / 4; 
931	} else {
932		n = p / (2 * Math.PI) * Math.asin(1 / a);
933	}
934	return -(a * Math.pow(2, 10 * (s -= 1)) * Math.sin((s - 1) * (2 * Math.PI) / p));
935});
936
937/**
938 * easeOutElastic 이펙트 함수
939 */
940collie.Effect.easeOutElastic = collie.Effect(function(s) {
941	var p = 0, a = 0, n;
942	if (s === 0) {
943		return 0;
944	}
945	if ((s/=1) == 1) {
946		return 1;
947	}
948	if (!p) {
949		p = 0.3;
950	}
951	if (!a || a < 1) { 
952		a = 1; n = p / 4; 
953	} else {
954		n = p / (2 * Math.PI) * Math.asin(1 / a);
955	}
956	return (a * Math.pow(2, -10 * s) * Math.sin((s - n) * (2 * Math.PI) / p ) + 1);
957});
958/**
959 * easeInOutElastic 이펙트 함수
960 */
961collie.Effect.easeInOutElastic = collie.Effect(function(s) {
962	var p = 0, a = 0, n;
963	if (s === 0) {
964		return 0;
965	}
966	if ((s/=1/2) == 2) {
967		return 1;
968	}
969	if (!p) {
970		p = (0.3 * 1.5);
971	}
972	if (!a || a < 1) { 
973		a = 1; n = p / 4; 
974	} else {
975		n = p / (2 * Math.PI) * Math.asin(1 / a);
976	}
977	if (s < 1) {
978		return -0.5 * (a * Math.pow(2, 10 * (s -= 1)) * Math.sin( (s - n) * (2 * Math.PI) / p ));
979	}
980	return a * Math.pow(2, -10 * (s -= 1)) * Math.sin( (s - n) * (2 * Math.PI) / p ) * 0.5 + 1;
981});
982
983/**
984 * easeOutBounce 이펙트 함수
985 */
986collie.Effect.easeOutBounce = collie.Effect(function(s) {
987	if (s < (1 / 2.75)) {
988		return (7.5625 * s * s);
989	} else if (s < (2 / 2.75)) {
990		return (7.5625 * (s -= (1.5 / 2.75)) * s + 0.75);
991	} else if (s < (2.5 / 2.75)) {
992		return (7.5625 * (s -= (2.25 / 2.75)) * s + 0.9375);
993	} else {
994		return (7.5625 * (s -= (2.625 / 2.75)) * s + 0.984375);
995	} 
996});
997/**
998 * easeInBounce 이펙트 함수
999 */
1000collie.Effect.easeInBounce = collie.Effect(function(s) {
1001	return 1 - collie.Effect.easeOutBounce(0, 1)(1 - s);
1002});
1003/**
1004 * easeInOutBounce 이펙트 함수
1005 */
1006collie.Effect.easeInOutBounce = collie.Effect(function(s) {
1007	return (s < 0.5) ? collie.Effect.easeInBounce(0, 1)(2 * s) * 0.5 : collie.Effect.easeOutBounce(0, 1)((2 * s) - 1) * 0.5 + 0.5;
1008});
1009
1010/**
1011 * easeInExpo 이펙트 함수
1012 */
1013collie.Effect.easeInExpo = collie.Effect(function(s) {
1014	return (s === 0) ? 0 : Math.pow(2, 10 * (s - 1));
1015});
1016/**
1017 * easeOutExpo 이펙트 함수
1018 */
1019collie.Effect.easeOutExpo = collie.Effect(function(s) {
1020	return (s == 1) ? 1 : -Math.pow(2, -10 * s / 1) + 1;
1021});
1022/**
1023 * easeInOutExpo 이펙트 함수
1024 */
1025collie.Effect.easeInOutExpo = collie.Effect(function(s) {
1026	return (s < 0.5) ? collie.Effect.easeInExpo(0, 1)(2 * s) * 0.5 : collie.Effect.easeOutExpo(0, 1)((2 * s) - 1) * 0.5 + 0.5;
1027});
1028/**
1029 * easeOutExpo 이펙트 함수
1030 */
1031collie.Effect.easeOutInExpo = collie.Effect(function(s) {
1032	return (s < 0.5) ? collie.Effect.easeOutExpo(0, 1)(2 * s) * 0.5 : collie.Effect.easeInExpo(0, 1)((2 * s) - 1) * 0.5 + 0.5;
1033});
1034
1035/**
1036 * Cubic-Bezier curve
1037 * @param {Number} x1
1038 * @param {Number} y1
1039 * @param {Number} x2
1040 * @param {Number} y2
1041 * @see http://www.netzgesta.de/dev/cubic-bezier-timing-function.html
1042 */
1043collie.Effect._cubicBezier = function(x1, y1, x2, y2){
1044	return function(t){
1045		var cx = 3.0 * x1, 
1046	    	bx = 3.0 * (x2 - x1) - cx, 
1047	    	ax = 1.0 - cx - bx, 
1048	    	cy = 3.0 * y1, 
1049	    	by = 3.0 * (y2 - y1) - cy, 
1050	    	ay = 1.0 - cy - by;
1051		
1052	    function sampleCurveX(t) {
1053	    	return ((ax * t + bx) * t + cx) * t;
1054	    }
1055	    function sampleCurveY(t) {
1056	    	return ((ay * t + by) * t + cy) * t;
1057	    }
1058	    function sampleCurveDerivativeX(t) {
1059	    	return (3.0 * ax * t + 2.0 * bx) * t + cx;
1060	    }
1061	    function solveCurveX(x,epsilon) {
1062	    	var t0, t1, t2, x2, d2, i;
1063	    	for (t2 = x, i = 0; i<8; i++) {
1064	    		x2 = sampleCurveX(t2) - x; 
1065	    		if (Math.abs(x2) < epsilon) {
1066	    			return t2;
1067	    		} 
1068	    		d2 = sampleCurveDerivativeX(t2); 
1069	    		if(Math.abs(d2) < 1e-6) {
1070	    			break;
1071	    		} 
1072	    		t2 = t2 - x2 / d2;
1073	    	}
1074		    t0 = 0.0; 
1075		    t1 = 1.0; 
1076		    t2 = x; 
1077		    if (t2 < t0) {
1078		    	return t0;
1079		    } 
1080		    if (t2 > t1) {
1081		    	return t1;
1082		    }
1083		    while (t0 < t1) {
1084		    	x2 = sampleCurveX(t2); 
1085		    	if (Math.abs(x2 - x) < epsilon) {
1086		    		return t2;
1087		    	} 
1088		    	if (x > x2) {
1089		    		t0 = t2;
1090		    	} else {
1091		    		t1 = t2;
1092		    	} 
1093		    	t2 = (t1 - t0) * 0.5 + t0;
1094		    }
1095	    	return t2; // Failure.
1096	    }
1097	    return sampleCurveY(solveCurveX(t, 1 / 200));
1098	};
1099};
1100
1101/**
1102 * Cubic-Bezier 함수를 생성한다.
1103 * @see http://en.wikipedia.org/wiki/B%C3%A9zier_curve
1104 * @param {Number} x1 control point 1의 x좌표
1105 * @param {Number} y1 control point 1의 y좌표
1106 * @param {Number} x2 control point 2의 x좌표
1107 * @param {Number} y2 control point 2의 y좌표
1108 * @return {Function} 생성된 이펙트 함수
1109 */
1110collie.Effect.cubicBezier = function(x1, y1, x2, y2){
1111	return collie.Effect(collie.Effect._cubicBezier(x1, y1, x2, y2));
1112};
1113
1114/**
1115 * Cubic-Bezier 커브를 이용해 CSS3 Transition Timing Function과 동일한 ease 함수
1116 * collie.Effect.cubicBezier(0.25, 0.1, 0.25, 1);
1117 * @see http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
1118 */
1119collie.Effect.cubicEase = collie.Effect.cubicBezier(0.25, 0.1, 0.25, 1);
1120
1121/**
1122 * Cubic-Bezier 커브를 이용해 CSS3 Transition Timing Function과 동일한 easeIn 함수
1123 * collie.Effect.cubicBezier(0.42, 0, 1, 1);
1124 * @see http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
1125 */
1126collie.Effect.cubicEaseIn = collie.Effect.cubicBezier(0.42, 0, 1, 1);
1127
1128/**
1129 * Cubic-Bezier 커브를 이용해 CSS3 Transition Timing Function과 동일한 easeOut 함수
1130 * collie.Effect.cubicBezier(0, 0, 0.58, 1);
1131 * @see http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
1132 */
1133collie.Effect.cubicEaseOut = collie.Effect.cubicBezier(0, 0, 0.58, 1);
1134
1135/**
1136 * Cubic-Bezier 커브를 이용해 CSS3 Transition Timing Function과 동일한 easeInOut 함수
1137 * collie.Effect.cubicBezier(0.42, 0, 0.58, 1);
1138 * @see http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
1139 */
1140collie.Effect.cubicEaseInOut = collie.Effect.cubicBezier(0.42, 0, 0.58, 1);
1141
1142/**
1143 * Cubic-Bezier 커브를 이용해 easeOutIn 함수를 구한다.
1144 * collie.Effect.cubicBezier(0, 0.42, 1, 0.58);
1145 */
1146collie.Effect.cubicEaseOutIn = collie.Effect.cubicBezier(0, 0.42, 1, 0.58);
1147
1148/**
1149 * overphase 이펙트 함수
1150 */
1151collie.Effect.overphase = collie.Effect(function(s){
1152	s /= 0.652785;
1153	return (Math.sqrt((2 - s) * s) + (0.1 * s)).toFixed(5);	
1154});
1155
1156/**
1157 * sin 곡선의 일부를 이용한 sinusoidal 이펙트 함수
1158 */
1159collie.Effect.sinusoidal = collie.Effect(function(s) {
1160	return (-Math.cos(s * Math.PI) / 2) + 0.5;
1161});
1162
1163/**
1164 * mirror 이펙트 함수
1165 * sinusoidal 이펙트 함수를 사용한다.
1166 */
1167collie.Effect.mirror = collie.Effect(function(s) {
1168	return (s < 0.5) ? collie.Effect.sinusoidal(0, 1)(s * 2) : collie.Effect.sinusoidal(0, 1)(1 - (s - 0.5) * 2);
1169});
1170
1171/**
1172 * nPulse의 진동수를 가지는 cos 함수를 구한다.
1173 * @param {Number} nPulse 진동수
1174 * @return {Function} 생성된 이펙트 함수
1175 * @example
1176var f = collie.Effect.pulse(3); //진동수 3을 가지는 함수를 리턴
1177//시작 수치값과 종료 수치값을 설정해 collie.Effect 함수를 생성
1178var fEffect = f(0, 100);
1179fEffect(0); => 0
1180fEffect(1); => 100
1181 */
1182collie.Effect.pulse = function(nPulse) {
1183    return collie.Effect(function(s){
1184		return (-Math.cos((s * (nPulse - 0.5) * 2) * Math.PI) / 2) + 0.5;	
1185	});
1186};
1187
1188/**
1189 * nPeriod의 주기와 nHeight의 진폭을 가지는 sin 함수를 구한다.
1190 * @param {Number} nPeriod 주기
1191 * @param {Number} nHeight 진폭
1192 * @return {Function} 생성된 이펙트 함수
1193 * @example
1194var f = collie.Effect.wave(3, 1); //주기 3, 높이 1을 가지는 함수를 리턴
1195//시작 수치값과 종료 수치값을 설정해 collie.Effect 함수를 생성
1196var fEffect = f(0, 100);
1197fEffect(0); => 0
1198fEffect(1); => 0
1199 */
1200collie.Effect.wave = function(nPeriod, nHeight) {
1201    return collie.Effect(function(s){
1202    	return (nHeight || 1) * (Math.sin(nPeriod * (s * 360) * Math.PI / 180)).toFixed(5);
1203	});
1204};
1205
1206/**
1207 * easeIn 이펙트 함수
1208 * easeInCubic 함수와 동일하다.
1209 * @see easeInCubic
1210 */
1211collie.Effect.easeIn = collie.Effect.easeInCubic;
1212/**
1213 * easeOut 이펙트 함수
1214 * easeOutCubic 함수와 동일하다.
1215 * @see easeOutCubic
1216 */
1217collie.Effect.easeOut = collie.Effect.easeOutCubic;
1218/**
1219 * easeInOut 이펙트 함수
1220 * easeInOutCubic 함수와 동일하다.
1221 * @see easeInOutCubic
1222 */
1223collie.Effect.easeInOut = collie.Effect.easeInOutCubic;
1224/**
1225 * easeOutIn 이펙트 함수
1226 * easeOutInCubic 함수와 동일하다.
1227 * @see easeOutInCubic
1228 */
1229collie.Effect.easeOutIn = collie.Effect.easeOutInCubic;
1230/**
1231 * bounce 이펙트 함수
1232 * easeOutBounce 함수와 동일하다.
1233 * @see easeOutBounce
1234 */
1235collie.Effect.bounce = collie.Effect.easeOutBounce;
1236/**
1237 * elastic 이펙트 함수
1238 * easeInElastic 함수와 동일하다.
1239 * @see easeInElastic
1240 */
1241collie.Effect.elastic = collie.Effect.easeInElastic;
1242/**
1243 * 별도의 이벤트를 다룰 수 있고 옵션 값을 갖는 컴포넌트 클래스
1244 * @class collie.Component
1245 */
1246collie.Component = collie.Class(/** @lends collie.Component.prototype */{
1247	/**
1248	 * @constructs
1249	 */
1250	$init : function () {
1251		this._bInitOption = false;
1252		this._htOption = {};
1253		this._htOptionSetter = {};
1254		this._htHandler = {};
1255	},
1256	
1257	/**
1258	 * 컴포넌트의 옵션을 설정한다.
1259	 * @example
1260	 * component.option({
1261	 * 	a : 1,
1262	 * 	b : true
1263	 * });
1264	 * 
1265	 * component.option("a", 1);
1266	 * component.option("a"); // return 1
1267	 * @param {Object|String} vName 옵션 이름이나 여러 옵션을 설정할 수 있는 객체를 넣을 수 있다.
1268	 * @param {Variables} [vValue] 옵션 값, 값이 없다면 해당 옵션 값을 반환한다.
1269	 * @param {Boolean} [bNotOverwrite] 이 값이 true면 기존에 값이 있을 경우 덮이 씌우지 않는다
1270	 */
1271	option : function (vName, vValue, bNotOverwrite) {
1272		if (typeof vName === "object") {
1273			// 초기에 넣을 때는 기본 값으로 설정
1274			if (!this._bInitOption) {
1275				this._htOption = collie.util.cloneObject(vName);
1276				this._bInitOption = true;
1277			} else {
1278				for (var i in vName) {
1279					this.option(i, vName[i], bNotOverwrite);
1280				}
1281			}
1282		} else if (typeof vName === "string") {
1283			// setter
1284			if (vValue !== undefined) {
1285				if (!bNotOverwrite || typeof this._htOption[vName] === "undefined") {
1286					this._htOption[vName] = vValue;
1287					
1288					if (this._htOptionSetter[vName] !== undefined) {
1289						this._htOptionSetter[vName](vValue);
1290					}
1291					
1292					this._bInitOption = true;
1293				}
1294			} else { // getter
1295				return this._htOption[vName];
1296			}
1297		} else {
1298			return this._htOption;
1299		}
1300	},
1301	
1302	/**
1303	 * DisplayObject와 Layer의 서로 다른 인터페이스를 맞추기 위한 임시 메서드
1304	 * 
1305	 * @see collie.Component#option
1306	 * @param {String} sName
1307	 * @return {Variables}
1308	 */
1309	get : function (sName) {
1310		return this.option(sName);
1311	},
1312	
1313	/**
1314	 * DisplayObject와 Layer의 서로 다른 인터페이스를 맞추기 위한 임시 메서드
1315	 * 
1316	 * @see collie.Component#option
1317	 * @param {String} sName
1318	 * @param {Variables} vValue
1319	 * @param {Boolean} [bNotOverwrite]
1320	 * @return {Object} For method chaining
1321	 */
1322	set : function (sName, vValue, bNotOverwrite) {
1323		this.option(sName, vValue, bNotOverwrite);
1324		return this;
1325	},
1326	
1327	/**
1328	 * 옵션을 제거한다
1329	 * 
1330	 * @param {String} sKey
1331	 */
1332	unset : function (sKey) {
1333		if (this._htOption && typeof this._htOption[sKey] !== "undefined") {
1334			delete this._htOption[sKey];
1335		}
1336	},
1337	
1338	/**
1339	 * 옵션 값이 설정될 때 실행될 함수를 지정한다. Setter는 한 속성 당 한 개의 함수만 설정할 수 있다.
1340	 * 
1341	 * @param {String} sName
1342	 * @param {Function} fSetter
1343	 */
1344	optionSetter : function (sName, fSetter) {
1345		this._htOptionSetter[sName] = fSetter;
1346	},
1347	
1348	/**
1349	 * 이벤트 발생
1350	 * 
1351	 * @param {String} sName
1352	 * @param {Object} oEvent
1353	 * @return {Boolean} 이벤트 발생 중 collie.ComponentEvent의 stop 메소드가 실행될 경
1353우 false를 반환한다
1354	 */
1355	fireEvent : function (sName, oEvent) {
1356		if (typeof this._htHandler[sName] !== "undefined" && this._htHandler[sName].length > 0) {
1357			oEvent = oEvent || {};
1358			oCustomEvent = new collie.ComponentEvent(sName, oEvent);
1359			var aHandler = this._htHandler[sName].concat();
1360			var bCanceled = false;
1361			
1362			for (var i = 0, len = aHandler.length; i < len; i++) {
1363				this._htHandler[sName][i](oCustomEvent);
1364				
1365				// stop했으면 false를 반환
1366				if (oCustomEvent.isStop()) {
1367					bCanceled = true;
1368				}
1369			}
1370			
1371			if (bCanceled) {
1372				return false;
1373			}
1374		}
1375		
1376		return true;
1377	},
1378	
1379	/**
1380	 * 이벤트 핸들러 추가
1381	 * 
1382	 * @param {Object|String} vEvent
1383	 * @param {Function} fHandler
1384	 * @return {collie.Component} 메소드 체이닝 지원
1385	 */
1386	attach : function (vEvent, fHandler) {
1387		if (typeof vEvent !== "string") {
1388			for (var i in vEvent) {
1389				this.attach(i, vEvent[i]);
1390			}
1391		} else {
1392			this._htHandler[vEvent] = this._htHandler[vEvent] || [];
1393			var aHandler = this._htHandler[vEvent];
1394			
1395			// 핸들러가 있을 때만 등록
1396			if (!fHandler) {
1397				return this;
1398			}
1399			
1400			// 중복된 핸들러는 등록하지 않음
1401			for (var i = 0, len = aHandler.length; i < len; i++) {
1402				if (aHandler[i] === fHandler) {
1403					return this;
1404				}
1405			}
1406			
1407			// 핸들러 등록
1408			aHandler.push(fHandler);
1409		}
1410		
1411		return this;
1412	},
1413	
1414	/**
1415	 * 이벤트 핸들러를 해제한다
1416	 * 
1417	 * @param {Object|String} vEvent
1418	 * @param {Function} fHandler 값이 없을 경우 이 이벤트에 할당된 전체 핸들러를 해제한다
1419	 */
1420	detach : function (vEvent, fHandler) {
1421		if (typeof vEvent !== "string") {
1422			for (var i in vEvent) {
1423				this.detach(i, vEvent[i]);
1424			}
1425		} else if (this._htHandler[vEvent] !== undefined) {
1426			var aHandler = this._htHandler[vEvent];
1427			
1428			// 두번째 인자가 없을 때 전체를 detach
1429			if (!fHandler) {
1430				delete this._htHandler[vEvent];
1431			} else {
1432				for (var i = 0, len = aHandler.length; i < len; i++) {
1433					if (aHandler[i] === fHandler) {
1434						this._htHandler[vEvent].splice(i, 1);
1435						
1436						// 배열이 다 없어졌다면 제거
1437						if (this._htHandler[vEvent].length < 1) {
1438							delete this._htHandler[vEvent];
1439						}
1440						break;
1441					}
1442				}
1443			}
1444		}
1445	},
1446	
1447	/**
1448	 * 모든 이벤트 핸들러를 해제
1449	 * 
1450	 * @param {String} sName 이벤트 이름, 값이 없으면 이 컴포넌트에 할당된 모든 이벤트를 해제한다
1451	 */
1452	detachAll : function (sName) {
1453		if (sName) {
1454			if (this._htHandler[sName] !== undefined) {
1455				this._htHandler[sName] = [];
1456			}
1457		} else {
1458			this._htHandler = {};
1459		}
1460	}
1461});
1462
1463/**
1464 * 컴포넌트 클래스의 이벤트가 발생될 때 생성되는 이벤트 클래스
1465 * @class
1466 * @private
1467 * @param {String} sName 이벤트 이름
1468 * @param {Object} oEvent
1469 */
1470collie.ComponentEvent = collie.Class(/** @lends collie.ComponentEvent.prototype */{
1471	/**
1472	 * @constructs
1473	 */
1474	$init : function (sName, oEvent) {
1475		this.type = sName;
1476		this._bCanceled = false;
1477		
1478		//TODO 향후에 이 구조를 바꾸는게 좋음
1479		if (oEvent) {
1480			for (var i in oEvent) {
1481				this[i] = oEvent[i];
1482			}
1483		}
1484	},
1485	
1486	/**
1487	 * 이벤트를 멈추고 싶은 경우 실행
1488	 */
1489	stop : function () {
1490		this._bCanceled = true;
1491	},
1492	
1493	/**
1494	 * 이벤트가 멈췄는지 확인
1495	 * 
1496	 * @return {Boolean} 멈췄으면 true
1497	 */
1498	isStop : function () {
1499		return this._bCanceled;
1500	}
1501});
1502/**
1503 * @class
1504 * @private
1505 * @example
1506 * collie.ImageManager.add({
1507 * 	"sample" : "sample.png"
1508 * });
1509 * collie.ImageManager.addSprite("sample", {
1510 * 	normal : [0, 0],
1511 * 	action : [30, 0]
1512 * });
1513 * new collie.DisplayObject({
1514 * 	spriteSheet : "normal",
1515 * 	backgroundImage : "sample"
1516 * });
1517 */
1518collie.SpriteSheet = collie.Class(/** @lends collie.SpriteSheet.prototype */{
1519	$init : function () {
1520		this._htSpriteSheet = {};
1521	},
1522
1523	/**
1524	 * 스프라이트를 추가
1525	 * 
1526	 * @param {String} sImageName collie.ImageManager에 등록된 이미지 이름
1527	 * @param {String|Object} vSpriteName 객체로 한 번에 여러 개의 정보를 등록할 수 있음
1528	 * @param {Number} nOffsetX
1529	 * @param {Number} nOffsetY
1530	 * @param {Number} [nWidth]
1531	 * @param {Number} [nHeight]
1532	 * @param {Number} [nSpriteLength]
1533	 */
1534	add : function (sImageName, vSpriteName, nOffsetX, nOffsetY, nWidth, nHeight, nSpriteLength) {
1535		if (typeof vSpriteName === "object") {
1536			if (vSpriteName instanceof Array) {
1537				for (var i = 0, l = vSpriteName.length; i < l; i++) {
1538					this.add.apply(this, [sImageName, i].concat(vSpriteName[i]));
1539				}
1540			} else {
1541				for (var i in vSpriteName) {
1542					this.add.apply(this, [sImageName, i].concat(vSpriteName[i]));
1543				}
1544			}
1545		} else {
1546			this._htSpriteSheet[sImageName] = this._htSpriteSheet[sImageName] || {};
1547			
1548			if (typeof nWidth !== "undefined") {
1549				collie.ImageManager.getImage(sImageName, function (el) {
1550					this._addWithSpriteLength(el, sImageName, vSpriteName, nOffsetX, nOffsetY, nWidth, nHeight, nSpriteLength);
1551				}.bind(this));
1552			} else {
1553				this._htSpriteSheet[sImageName][vSpriteName] = [nOffsetX, nOffsetY];
1554			}
1555		}
1556	},
1557	
1558	/**
1559	 * @private
1560	 */
1561	_addWithSpriteLength : function (elImage, sImageName, sSpriteName, nOffsetX, nOffsetY, nWidth, nHeight, nSpriteLength) {
1562		var aSpriteList = this._htSpriteSheet[sImageName][sSpriteName] = [];
1563		var nImageWidth = elImage.width;
1564		var nImageHeight = elImage.height;
1565		
1566		// 레티나 이미지면 반으로 나눔
1567		if (collie.Renderer.isRetinaDisplay()) {
1568			nImageWidth /= 2;
1569			nImageHeight /= 2;
1570		}
1571		
1572		var x = nOffsetX;
1573		var y = nOffsetY;
1574		
1575		for (i = 0; i < nSpriteLength; i++) {
1576			// 이미지를 넘어서면 줄을 바꿈
1577			// 다음 줄은 nOffsetX 부터 시작하는 것이 아니라 0부터 시작함
1578			if (x >= nImageWidth) {
1579				x = 0;
1580				y += nHeight;
1581			}
1582			
1583			// 이미지를 넘어서면 끝남
1584			if (y >= nImageHeight) {
1585				break;
1586			}
1587			
1588			aSpriteList.push([x, y]);
1589			x += nWidth;
1590		}
1591	},
1592	
1593	/**
1594	 * 해당 이미지에 등록돼 있는 스프라이트 정보를 제거
1595	 * 
1596	 * @param {String} sImageName collie.ImageManager에 등록된 이미지 이름
1597	 */
1598	remove : function (sImageName) {
1599		if (this._htSpriteSheet[sImageName]) {
1600			delete this._htSpriteSheet[sImageName];
1601		}
1602	},
1603	
1604	/**
1605	 * SpriteSheet 정보를 반환
1606	 * 
1607	 * @param {String} sImageName collie.ImageManager에 등록된 이미지 이름
1608	 * @return {Object}
1609	 */
1610	get : function (sImageName) {
1611		return this._htSpriteSheet[sImageName] ? this._htSpriteSheet[sImageName] : false;
1612	},
1613	
1614	/**
1615	 * 스프라이트 시트 정보를 초기화 한다
1616	 */
1617	reset : function () {
1618		this._htSpriteSheet = {};
1619	}
1620});
1621/**
1622 * 이미지 리소스 관리
1623 * @example
1624 * // 한 개의 이미지를 로딩
1625 * collie.ImageManager.add("key", "sample.png");
1626 * new collie.DisplayObject({
1627 * 	backgroundImage: "key"
1628 * });
1629 * @example
1630 * // 여러 이미지를 한 번에 로딩
1631 * collie.ImageManager.add({
1632 * 	image1 : "image1.png",
1633 * 	image2 : "image2.png"
1634 * }, function () {
1635 * 	alert("complete");
1636 * });
1637 * @namespace
1638 */
1639collie.ImageManager = collie.ImageManager || new (collie.Class(/** @lends collie.ImageManager */{
1640	/**
1641	 * 이미지 로딩 실패시 재시도 횟수
1642	 * @type {Number}
1643	 */
1644	RETRY_COUNT : 3,
1645	
1646	/**
1647	 * 이미지 로딩 실패시 재시도 딜레이 ms
1648	 * @type {Number}
1649	 */
1650	RETRY_DELAY : 500,
1651	
1652	/**
1653	 * DOM일 때 css3d 엘리먼트를 미리 만들어놓는지 여부
1654	 * - 기능 불안정으로 기본 사용 false로 변경, top, left를 멀리 보내버리면 3d 렌더링에 부담이 될 수 있다.
1655	 * @type {Boolean} 
1656	 */
1657	USE_PRERENDERING_DOM : false,
1658	
1659	$init : function () {
1660		this._aImages = [];
1661		this._htImageNames = {};
1662		this._htImageRetryCount = {};
1663		this._htImageWhileLoading = {};
1664		this._nCount = 0;
1665		this._oSpriteSheet = new collie.SpriteSheet();
1666	},
1667	
1668	/**
1669	 * 이미지를 추가
1670	 * - 외부에서 직접 사용하면 count가 어긋나기 때문에 private 처리
1671	 * 
1672	 * @private
1673	 * @param {String} sName 리소스 이름, 나중에 이 이름으로 리소스를 찾는다
1674	 * @param {HTMLElement} elImage 저장할 엘리먼트
1675	 */
1676	_addImage : function (elImage, sName) {
1677		var nLength = this._aImages.push({
1678			element : elImage,
1679			name : sName
1680		});
1681		
1682		var aCallback = this._htImageNames[sName];
1683		this._htImageNames[sName] = nLength - 1;
1684		delete this._htImageRetryCount[sName];
1685		
1686		// callback 실행
1687		if (aCallback && aCallback instanceof Array) {
1688			for (var i = 0, len = aCallback.length; i < len; i++) {
1689				aCallback[i](elImage, sName);
1690			}
1691			
1692			aCallback = null;
1693		}
1694		
1695		/**
1696		 * 한개의 이미지가 로딩되었을 때 발생
1697		 * @name collie.ImageManager#process
1698		 * @event
1699		 * @param {Object} oEvent
1700		 * @param {String} oEvent.name 이미지 이름
1701		 * @param {String} oEvent.url 이미지 URL
1702		 * @param {Number} oEvent.count 현재 로딩된 갯수
1703		 * @param {Number} oEvent.total 전체 이미지 갯수
1704		 * @param {Number} oEvent.ratio 로딩된 이미지의 비율 (0~1)
1705		 */
1706		this.fireEvent("process", {
1707			name : sName,
1708			url : elImage.src,
1709			count : nLength,
1710			total : this._nCount,
1711			ratio : Math.round((nLength / this._nCount) * 1000) / 1000
1712		});
1713		
1714		if (this._nCount === nLength) {
1715			/**
1716			 * 등록된 이미지가 모두 로드 되었을 경우
1717			 * @name collie.ImageManager#complete
1718			 * @event
1719			 * @param {Object} oEvent
1720			 */
1721			this.fireEvent("complete");
1722		}
1723	},
1724	
1725	/**
1726	 * 자리를 찜, 이미 자리가 있는 경우에는 아무것도 하
1726지 않는다
1727	 * 
1728	 * @private
1729	 * @param {String} sName 이미지 이름
1730	 */
1731	_markImage : function (sName) {
1732		if (!this._htImageNames[sName]) {
1733			this._htImageNames[sName] = [];
1734		}
1735		
1736		if (!this._htImageRetryCount[sName]) {
1737			this._htImageRetryCount[sName] = 0;
1738		} 
1739	},
1740	
1741	/**
1742	 * 해쉬를 다시 만듦
1743	 * @private
1744	 */
1745	_makeHash : function () {
1746		this._htImageNames = {};
1747		
1748		for (var i = 0, len = this._aImages.length; i < len; i++) {
1749			this._htImageNames[this._aImages[i].name] = i;
1750		}
1751	},
1752	
1753	/**
1754	 * 이미지를 가져온다
1755	 * 
1756	 * @static
1757	 * @param {String} sName 리소스 이름
1758	 * @param {Function} fCallback 리소스가 로드되지 않았을 수도 있으므로 콜백으로 처리
1759	 * @return {HTMLElement}
1760	 */
1761	getImage : function (sName, fCallback) {
1762		if (!sName && sName !== 0) {
1763			return false;
1764		}
1765		
1766		// 마크되지 않은 이름이라면 마크함
1767		if (!(sName in this._htImageNames)) {
1768			this._markImage(sName);
1769		}
1770		
1771		// 마크가 된 상황이고 아직 로딩되지 않았다면
1772		if (this._htImageNames[sName] instanceof Array) {
1773			return (fCallback && this._addMarkCallback(sName, fCallback));
1774		} else {
1775			if (fCallback) {
1776				fCallback(this._aImages[this._htImageNames[sName]].element);
1777			} else {
1778				return this._aImages[this._htImageNames[sName]].element;
1779			}
1780		}
1781	},
1782	
1783	/**
1784	 * 마크된 영역에 콜백을 등록, 로딩이 완료되면 콜백이 실행된다
1785	 * @private
1786	 * @param {String} sName
1787	 * @param {Function} fCallback
1788	 * @param {Function} fFail
1789	 * @return {Boolean} callback이 등록될 경우 true를 반환
1790	 */
1791	_addMarkCallback : function (sName, fCallback, fFail) {
1792		if ((sName in this._htImageNames) && this._htImageNames[sName] instanceof Array) {
1793			if (fFail) {
1794				var fError = function fError(oEvent) {
1795					if (oEvent.name === sName) {
1796						fFail();
1797						this.detach("error", fError);
1798					}
1799				};
1800				
1801				this.attach("error", fError);
1802			}
1803			
1804			if (fCallback) {
1805				this._htImageNames[sName].push(fCallback);
1806			}
1807			
1808			return true;
1809		} else {
1810			return false;
1811		}
1812	},
1813	
1814	/**
1815	 * 이미지를 삭제한다
1816	 * @param {String} sName 리소스 이름
1817	 */
1818	removeImage : function (sName) {
1819		if (!(sName in this._htImageNames)) {
1820			return false;
1821		}
1822		
1823		var elImage = this._aImages.splice(this._htImageNames[sName], 1);
1824		this._makeHash();
1825		elImage.onload = null;
1826		elImage.onerror = null;
1827		elImage.src = null;
1828		elImage = null;
1829		this._oSpriteSheet.remove(sName);
1830	},
1831	
1832	/**
1833	 * 이미지를 삭제한다
1834	 * @see collie.ImageManager.removeImage
1835	 */
1836	remove : function (sName) {
1837		this.removeImage(sName);
1838	},
1839	
1840	/**
1841	 * 이미지 리소스를 추가한다
1842	 * 
1843	 * @example
1844	 * // 1개의 이미지를 추가
1845	 * collie.ImageManager.add("key", "sample.png", function () {
1846	 * 	// callback
1847	 * });
1848	 * @example
1849	 * // 여러 개의 이미지를 추가
1850	 * collie.ImageManager.add({
1851	 * 	key : "sample.png",
1852	 * 	key2 : "sample2.png"
1853	 * }, function () {
1854	 * 	// callback
1855	 * });
1856	 * 
1857	 * @see collie.ImageManager.addImage
1858	 * @see collie.ImageManager.addImages
1859	 */
1860	add : function () {
1861		if (typeof arguments[0] === "object") {
1862			this.addImages.apply(this, arguments);
1863		} else {
1864			this.addImage.apply(this, arguments);
1865		}
1866	},
1867	
1868	/**
1869	 * 여러 개의 이미지 리소스를 한번에 추가 한다.
1870	 * 
1871	 * @param {Object} htList { sName : sURL , sName2 : sURL2 }
1872	 * @param {Function} fCallback 선택한 파일이 모두 로드될 때 실행될 함수. 없으면 실행되지 않는다. 인자로 htList를 반환
1873	 * @param {Function} fFail 선택한 파일 중에 한개라도 로드되지 않았을 때 실행될 함수. 실패한 이미지의 [el, sName, sURL] 배열 목록을 인자로 갖는다
1874	 */
1875	addImages : function (htList, fCallback, fFail) {
1876		var fOnComplete = null;
1877		var fOnFail = null;
1878		var nTotalCount = 0;
1879		var nCurrentCount = 0;
1880		var aFailedImages = [];
1881		
1882		// 돌면서 갯수 먼저 파악
1883		for (var i in htList) {
1884			nTotalCount++;
1885		}
1886		
1887		// 콜백
1888		if (fCallback && fCallback !== null) {
1889			fOnComplete = (function () {
1890				nCurrentCount++;
1891				
1892				if (nCurrentCount >= nTotalCount) {
1893					fCallback(htList);	
1894				}
1895			}).bind(this);
1896		}
1897		
1898		// 실패했을 경우
1899		if (fFail && fFail !== null) {
1900			fOnFail = (function (el, sName, sURL) {
1901				aFailedImages.push([el, sName, sURL]);
1902				
1903				if (aFailedImages.length + nCurrentCount >= nTotalCount) {
1904					fFail(aFailedImages);
1905				}
1906			}).bind(this);
1907		}
1908		
1909		// 로드
1910		for (var i in htList) {
1911			this.addImage(i, htList[i], fOnComplete, fOnFail);
1912		}
1913	},
1914	
1915	/**
1916	 * 비동기로 이미지를 로딩
1917	 * 
1918	 * @param {String} sName 이미지 이름, 이름이 없을 경우 Loader에 저장하지 않는다
1919	 * @param {String} sURL 이미지 주소
1920	 * @param {Function} fCallback 성공시 실행될 함수
1921	 * @param {HTMLElement} fCallback.elImage 엘리먼트
1922	 * @param {String} fCallback.sName 리소스 이름
1923	 * @param {String} fCallback.sURL URL
1924	 * @param {Function} fFail 실패시 실행될 함수
1925	 */
1926	addImage : function (sName, sURL, fCallback, fFail) {
1927		// 이미 이미지가 있으면 바로 콜백 실행
1928		if (this.getImage(sName)) {
1929			if (fCallback && fCallback !== null) {
1930				fCallback(this.getImage(sName), sName, sURL);
1931			}
1932			return;
1933		}
1934		
1935		// 이미 로딩 중이고 마크가 된 상황이라면 콜백 등록하고 멈춤
1936		if ((sName in this._htImageWhileLoading) && this._addMarkCallback(sName, fCallback, fFail)) {
1937			return;
1938		}
1939		
1940		this._nCount++;
1941		this._markImage(sName);
1942		var el = new Image();
1943		
1944		// DOM모드면 미리 OpenGL 레이어로 변환해 놓는다
1945		if (this.USE_PRERENDERING_DOM && collie.Renderer.getRenderingMode() === "dom" && collie.util.getSupp
1945ortCSS3d() && !collie.util.getDeviceInfo().android) {
1946			el.style.webkitTransform = "translateZ(0)";
1947			el.style.position = "absolute";
1948			el.style.visibility = "hidden";
1949			collie.Renderer.getElement().appendChild(el);
1950		}
1951		
1952		this._htImageWhileLoading[sName] = el;
1953		
1954		el.onload = (function (e) {
1955			this._addImage(el, sName);
1956			
1957			if (fCallback && fCallback !== null) {
1958				fCallback(el, sName, sURL);
1959			}
1960			
1961			el.onerror = el.onload = null;
1962			this._deleteWhileLoading(sName);
1963		}).bind(this);
1964		
1965		el.onerror = (function (e) {
1966			// 재시도
1967			if (this._htImageRetryCount[sName] < this.RETRY_COUNT) {
1968				this._htImageRetryCount[sName]++;
1969				
1970				/**
1971				 * 한 개의 이미지가 로딩 실패 했을 때 실행
1972				 * @name collie.ImageManager#retry
1973				 * @event
1974				 * @param {Object} oEvent
1975				 * @param {String} oEvent.name 실패된 이미지 이름
1976				 * @param {String} oEvent.url 실패된 이미지 URL
1977				 * @param {Number} oEvent.count 현재 로딩된 갯수
1978				 * @param {Number} oEvent.total 전체 이미지 갯수
1979				 */
1980				this.fireEvent("retry", {
1981					count : this._aImages.length,
1982					total : this._nCount,
1983					name : sName,
1984					url : sURL,
1985					delay : this.RETRY_DELAY,
1986					retryCount : this._htImageRetryCount[sName]
1987				});
1988				
1989				setTimeout(function () {
1990					// workaround http://code.google.com/p/chromium/issues/detail?id=7731
1991					el.src = "about:blank";
1992					el.src = sURL;
1993				}, this.RETRY_DELAY);
1994				return;
1995			}
1996			
1997			if (fFail && fFail !== null) {
1998				fFail(el, sName, sURL);
1999			}
2000			
2001			/**
2002			 * 한 개의 이미지가 로딩 실패 했을 때 실행
2003			 * @name collie.ImageManager#error
2004			 * @event
2005			 * @param {Object} oEvent
2006			 * @param {String} oEvent.name 실패된 이미지 이름
2007			 * @param {String} oEvent.url 실패된 이미지 URL
2008			 * @param {Number} oEvent.count 현재 로딩된 갯수
2009			 * @param {Number} oEvent.total 전체 이미지 갯수
2010			 */
2011			this.fireEvent("error", {
2012				count : this._aImages.length,
2013				total : this._nCount,
2014				name : sName,
2015				url : sURL
2016			});
2017			
2018			el.onerror = el.onload = null;
2019			this._deleteWhileLoading(sName);
2020		}).bind(this);
2021		
2022		// Webkit 버그로 인해서 CORS 주석 처리
2023		// el.crossOrigin = "";
2024		el.src = sURL;
2025	},
2026	
2027	/**
2028	 * 로딩 중에 임시로 담아놓는 변수를 제거
2029	 * @private
2030	 * @param {String} sName
2031	 */
2032	_deleteWhileLoading : function (sName) {
2033		delete this._htImageWhileLoading[sName];
2034	},
2035	
2036	/**
2037	 * 로드되고 있는 파일을 모두 멈춤
2038	 */
2039	abort : function () {
2040		for (var i in this._htImageWhileLoading) {
2041			this._htImageWhileLoading[i].onload = this._htImageWhileLoading[i].onerror = null; 
2042			this._htImageWhileLoading[i].src = null;
2043			this._htImageWhileLoading[i] = null;
2044		}
2045		
2046		this._htImageWhileLoading = {};
2047		this._htImageStartedLoading = {};
2048	},
2049	
2050	/**
2051	 * 등록된 파일을 모두 제거
2052	 */
2053	reset : function () {
2054		this.abort();
2055		this._aImages = [];
2056		this._htImageNames = {};
2057		this._htImageRetryCount = {};
2058		this._htImageWhileLoading = {};
2059		this._nCount = 0;
2060		this._oSpriteSheet.reset();
2061	},
2062	
2063	/**
2064	 * 비동기로 등록된 이미지 콜백을 취소 한다
2065	 * DisplayObject에서 setImage처리할 때 자동으로 호출 된다
2066	 * @private
2067	 * @arguments collie.ImageManager.getImage
2068	 */
2069	cancelGetImage : function (sName, fCallback) {
2070		if (this._htImageNames[sName] instanceof Array) {
2071			for (var i = 0, len = this._htImageNames[sName].length; i < len; i++) {
2072				if (this._htImageNames[sName][i] === fCallback) {
2073					this._htImageNames[sName].splice(i, 1);
2074					return;
2075				}
2076			}
2077		}
2078	},
2079	
2080	/**
2081	 * 이미지에 스프라이트 시트 정보를 추가한다
2082	 * 
2083	 * @param {String} sImageName collie.ImageManager에 등록된 이미지 이름
2084	 * @param {String|Object} vSpriteName 객체로 한 번에 여러 개의 정보를 등록할 수 있음
2085	 * @example
2086	 * collie.ImageManager.add({
2087	 * 	"sample" : "sample.png"
2088	 * });
2089	 * 
2090	 * // Add Sprites with key-value object
2091	 * collie.ImageManager.addSprite("sample", {
2092	 * 	normal : [0, 0], // [offsetX, offsetY]
2093	 * 	action : [30, 0],
2094	 *  jump : [60, 0, 30, 30, 8] // [startOffsetX, startOffsetY, a width per one frame, a height per one frame, spriteLength] 
2095	 * });
2096	 * 
2097	 * // or Add Sprites with array
2098	 * collie.ImageManager.addSprite("sample", [
2099	 * 	[0, 0], // key 0
2100	 * 	[30, 0], // key 1
2101	 * 	[60, 0, 30, 30, 8] // key 2 and [startOffsetX, startOffsetY, a width per one frame, a height per one frame, spriteLength]
2102	 * ]);
2103	 * 
2104	 * var item = new collie.DisplayObject({
2105	 * 	spriteSheet : "normal", // or 0
2106	 * 	backgroundImage : "sample"
2107	 * });
2108	 * 
2109	 * // with Timer
2110	 * collie.Timer.cycle(item, 1000, {
2111	 * 	from: 0, 
2112	 * 	to: 1,
2113	 * 	set: "spriteSheet"
2114	 * });
2115	 * 
2116	 * // If you use five parameters in the addSprite method, you can use spriteX option with spriteSheet 
2117	 * item.set("spriteSheet", "jump");
2118	 * collie.Timer.cycle(item, 1000, {
2119	 * 	from: 0,
2120	 * 	to: 7 // spriteLength 8
2121	 * });
2122	 */
2123	addSprite : function (sImageName, vSpriteName, nOffsetX, nOffsetY) {
2124		this._oSpriteSheet.add(sImageName, vSpriteName, nOffsetX, nOffsetY);
2125	},
2126	
2127	/**
2128	 * 스프라이트 시트 정보를 반환한다
2129	 * 
2130	 * @private
2131	 * @param {String} sImageName collie.ImageManager에 등록된 이미지 이름
2132	 * @return {Object} 
2133	 */
2134	getSprite : function (sImageName) {
2135		return this._oSpriteSheet.get(sImageName);
2136	},
2137	
2138	/**
2139	 * 스프라이트 정보를 제거한다
2140	 * 
2141	 * @param {String} sImageName collie.ImageManager에 등록된 이미지 이름
2142	 */
2143	removeSprite : function (sImageName) {
2144		this._oSpriteSheet.remove(sImageName);
2145	}
2146	
2147	/**
2148	 * 이미지를 사용 가능한 상태로 미리 만들어 놓는다. mark된 이미지는 DisplayObject에서 사용할 수 있다
2149	 *
2150	 * @name collie.ImageManager.mark 
2151	 * @param {Object|String} vName 점찍을 이미지 이름, load에서 쓰이는 sName을 뜻하며 addImages의 HashTable 형태로 넣을 경우 키 값이 이름으로 들어가게 된다
2152	 * @deprecated 정의되지 않은 이름을 부를 때 자동으로 mark되도록 수정
2153	 */
2154	/**
2155	 * @name collie.ImageManager.load 
2156	 * @deprecated add로 변경
2157	 */
2158}, collie.Component))();
2159/**
2160 * 행렬
2161 * @namespace
2162 */
2163collie.Matrix = {
2164	/**
2165	 * 행렬 곱셈
2166	 * 
2167	 * @param {Array} a1
2168	 * @param {Array} a2
2169	 */
2170	multiple : function (a1, a2) {
2171		var matrix = [];
2172		
2173		for (var row2 = 0, len = a2.length; row2 < len; row2++) {
2174			var r = [];
2175			
2176			for (var col2 = 0, len2 = a2[0].length; col2 < len2; col2++) {
2177				var s = 0;
2178				
2179				for (var col1 = 0, len3 = a1[0].length; col1 < len3; col1++) {
2180					s += a1[row2][col1] * a2[col1][col2];
2181				}
2182				
2183				r.push(s);
2184			}
2185			
2186			matrix.push(r);
2187		}
2188		
2189		return matrix;
2190	},
2191	
2192	/**
2193	 * translate와 관련된 계산 행렬을 반환
2194	 * 
2195	 * @param {Number} nX
2196	 * @param {Number} nY
2197	 * @return {Array}
2198	 */
2199	translate : function (nX, nY) {
2200		return [
2201			[1, 0, nX],
2202			[0, 1, nY],
2203			[0, 0, 1]
2204		];
2205	},
2206	
2207	/**
2208	 * scale 계산 행렬 반환
2209	 * 
2210	 * @param {Number} nX
2211	 * @param {Number} nY
2212	 * @return {Array}
2213	 */
2214	scale : function (nX, nY) {
2215		return [
2216			[nX, 0, 0],
2217			[0, nY, 0],
2218			[0, 0, 1]
2219		];
2220	},
2221	
2222	/**
2223	 * 회전 계산 행렬 반환
2224	 * 
2225	 * @param {Number} nAngle
2226	 * @return {Array}
2227	 */
2228	rotate : function (nAngle) {
2229		var nRad = collie.util.toRad(nAngle);
2230		var nCos = Math.cos(nRad);
2231		var nSin = Math.sin(nRad);
2232		
2233		return [
2234			[nCos, -nSin, 0],
2235			[nSin, nCos, 0],
2236			[0, 0, 1]
2237		];
2238	},
2239	
2240	/**
2241	 * 대상 point를 변형
2242	 * 
2243	 * @param {Array} a 적용할 계산 행렬
2244	 * @param {Number} nX 대상 x좌표
2245	 * @param {Number} nY 대상 y좌표
2246	 * @return {Object} htResult
2247	 * @return {Number} htResult.x 변경된 x좌표
2248	 * @return {Number} htResult.y 변경된 y좌표
2249	 */
2250	transform : function (a, nX, nY) {
2251		var aResult = this.multiple(a, [
2252			[nX],
2253			[nY],
2254			[1]
2255		]);
2256		
2257		return {
2258			x : aResult[0][0],
2259			y : aResult[1][0]
2260		};
2261	}
2262};
2263/**
2264 * Transform Matrix
2265 * - 기본으로 상대 좌표로 계산한다
2266 * - getBoundary와 같은 특수한 경우만 절대좌표로 반환
2267 * - 나중에 IE filter로 사용할 때는 points에 절대좌표 기능을 넣어야 함
2268 * @namespace
2269 */
2270collie.Transform = {
2271	_htBoundary : {
2272		left : 0,
2273		top : 0,
2274		right : 0,
2275		bottom : 0
2276	},
2277	_bIsIEUnder8 : collie.util.getDeviceInfo().ie && collie.util.getDeviceInfo().ie < 9,
2278	
2279	/**
2280	 * Transform된 표시 객체의 Boundary를 반환 한다 (0, 0에서 시작)
2281	 * TODO Transform 상속 구현 안됨!
2282	 * 
2283	 * @param {collie.DisplayObject} oDisplayObject
2284	 * @param {Boolean} bWithPoints 좌표를 반환하는지 여부, Sensor의 box hittest에서 쓰임
2285	 * @return {Object} htResult 상대 좌표 영역
2286	 * @return {Number} htResult.left
2287	 * @return {Number} htResult.right
2288	 * @return {Number} htResult.top
2289	 * @return {Number} htResult.bottom
2290	 * @return {Number} htResult.isTransform
2291	 * @return {Number} htResult.points
2292	 */
2293	getBoundary : function (oDisplayObject, bWithPoints) {
2294		var htInfo = oDisplayObject.get();
2295		var aPoints = [[0, 0], [htInfo.width, 0], [htInfo.width, htInfo.height], [0, htInfo.height]];
2296		var aTransformedPoints = this.points(oDisplayObject, aPoints);
2297		var htBoundary = collie.util.getBoundary(aTransformedPoints);
2298		this._htBoundary.left = htBoundary.left;
2299		this._htBoundary.right = htBoundary.right;
2300		this._htBoundary.bottom = htBoundary.bottom;
2301		this._htBoundary.top = htBoundary.top;
2302		this._htBoundary.isTransform = this.isUseTransform(oDisplayObject);
2303		
2304		if (bWithPoints) {
2305			this._htBoundary.points = aTransformedPoints; // sensor용 point
2306		}
2307		
2308		return this._htBoundary;
2309	},
2310	
2311	/**
2312	 * 해당 표시 객체에 맞게 점들을 transform한 결과를 반환 한다
2313	 * 
2314	 * @param {collie.DisplayObject} oDisplayObject 대상 표시 객체
2315	 * @param {Array} aPoints transform을 적용할 점들 (ex: [[x1, y1], [x2, y2], ...])
2316	 */
2317	points : function (oDisplayObject, aPoints) {
2318		var aMatrix
2319		
2320		if (!this._bIsIEUnder8) {
2321			aMatrix = this.getMatrixRecusively(oDisplayObject);
2322		}
2323
2324		// 계산할 필요가 없다면 그대로 반환
2325		if (!aMatrix) {
2326			return aPoints;
2327		}
2328		
2329		var aPointsAfter = [];
2330		
2331		for (var i = 0, len = aPoints.length; i < len; i++) {
2332			var htPoint = collie.Matrix.transform(aMatrix, aPoints[i][0], aPoints[i][1]);
2333			aPointsAfter.push([htPoint.x, htPoint.y]);
2334		}
2335		
2336		return aPointsAfter;
2337	},
2338	
2339	/**
2340	 * 상속된 Transform을 적용한 Matrix를 반환
2341	 * TODO 속도 체크해 봐야 함!
2342	 * 
2343	 * @param {collie.DisplayObject} oDisplayObject 최하위 객체
2344	 * @return {Array} Matrix
2345	 */
2346	getMatrixRecusively : function (oDisplayObject) {
2347		var self = oDisplayObject;
2348		var aMatrix = null;
2349		var nX = 0;
2350		var nY = 0;
2351		
2352		while (self) {
2353			if (this.isUseTransform(self)) {
2354				var aSelfMatrix = this.getMatrix(self, nX, nY);
2355				aMatrix = aMatrix !== null ? collie.Matrix.multiple(aMatrix, aSelfMatrix) : aSelfMatrix;
2356			}
2357			
2358			nX -= self._htOption.x;
2359			nY -= self._htOption.y;
2360			self = self.getParent();
2361		}
2362		
2363		return aMatrix;
2364	},
2365	
2366	/**
2367	 * 대상 표시 객체에 맞는 Matrix를 구한다
2368	 * 상대좌표의 matrix로 반환되며 최종 결과의 translate는 별도로 적용해야 한다
2369	 * 
2370	 * @param {collie.DisplayObject} oDisplayObject
2371	 * @param {Number} nX 좌표 보정치
2372	 * @param {Number} nY
2373	 * @return {Array} Matrix
2374	 */
2375	getMatrix : function (oDisplayObject, nX, nY) {
2376		if (typeof nX === "undefined") {
2377			nX = 0;
2378		}
2379		
2380		if (typeof nY === "undefined") {
2381			nY = 0;
2382		}
2383		
2384		var htOrigin = oDisplayObject.getOrigin();
2385		var htInfo = oDisplayObject.get();
2386		var aMatrix = collie.Matrix.translate(htOrigin.x + nX, htOrigin.y + nY);
2387		
2388		if (htInfo.angle !== 0) {
2389			aMatrix = collie.Matrix.multiple(aMatrix, collie.Matrix.rotate(htInfo.angle));
2390		}
2391		
2392		if (htInfo.scaleX !== 1 || htInfo.scaleY !== 1) {
2393			aMatrix = collie.Matrix.multiple(aMatrix, collie.Matrix.scale(htInfo.scaleX, htInfo.scaleY));
2394		}
2395		
2396		aMatrix = collie.Matrix.multiple(aMatrix, collie.Matrix.translate(-(htOrigin.x + nX), -(htOrigin.y + nY)));
2397		return aMatrix;
2398	},
2399	
2400	/**
2401	 * Transform을 사용하고 있는 경우
2402	 * @return {Boolean}
2403	 */
2404	isUseTransform : function (oDisplayObject) {
2405		return (oDisplayObject._htOption.scaleX !== 1 || oDisplayObject._htOption.scaleY !== 1 || oDisplayObject._htOption.angle !== 0);
2406	}
2407};
2408/**
2409 * 캔버스 방식의 렌더링
2410 * 
2411 * @private
2412 * @class collie.LayerCanvas
2413 * @param {collie.Layer} oLayer
2414 */
2415collie.LayerCanvas = collie.Class(/** @lends collie.LayerCanvas.prototype */{
2416	/**
2417	 * @private
2418	 * @constructs
2419	 */
2420	$init : function (oLayer) {
2421		this._oLayer = oLayer;
2422		this._oEvent = oLayer.getEvent();
2423		this._htDeviceInfo = collie.util.getDeviceInfo();
2424		this._initCanvas();
2425	},
2426	
2427	_initCanvas : function () {
2428		var htSize = this._getLayerSize();
2429		this._elCanvas = document.createElement("canvas");
2430		this._elCanvas.width = htSize.width;
2431		this._elCanvas.height = htSize.height;
2432		this._elCanvas.className = "_collie_layer";
2433		this._elCanvas.style.position = "absolute";
2434		this._elCanvas.style.left = this._oLayer.option("x") + "px";
2435		this._elCanvas.style.top = this._oLayer.option("y") + "px";
2436		
2437		if (collie.Renderer.isRetinaDisplay()) {
2438			this._elCanvas.style.width = (htSize.width / 2) + "px";
2439			this._elCanvas.style.height = (htSize.height / 2) + "px";
2440		}
2441		
2442		this._oContext = this._elCanvas.getContext('2d');
2443	},
2444	
2445	/**
2446	 * 현재 레이어 사이즈를 반환, 레티나일 경우에는 두배로 반환 한다 (캔버스일 경우에만)
2447	 * 
2448	 * @param {Number} nWidth 레이어 너비, 지정하지 않으면 Layer에서 값을 가져온다
2449	 * @param {Number} nHeight 레이어 높이, 지정하지 않으면 Layer에서 값을 가져온다
2450	 * @return {Object} htSize
2451	 * @return {Number} htSize.width
2452	 * @return {Number} htSize.height
2453	 */
2454	_getLayerSize : function (nWidth, nHeight) {
2455		nWidth = nWidth || this._oLayer.option("width");
2456		nHeight = nHeight || this._oLayer.option("height");
2457		
2458		// 레티나 디스플레이 대응
2459		if (collie.Renderer.isRetinaDisplay()) {
2460			nWidth *= 2;
2461			nHeight *= 2;
2462		}
2463		
2464		return {
2465			width : nWidth,
2466			height : nHeight
2467		};
2468	},
2469	
2470	/**
2471	 * Canvas Context를 반환
2472	 * @private
2473	 * @return {Boolean|Object}
2474	 */
2475	getContext : function () {
2476		return this._oContext;
2477	},
2478	
2479	/**
2480	 * @private
2481	 */
2482	getElement : function () {
2483		return this._elCanvas;
2484	},
2485
2486	/**
2487	 * @private
2488	 */
2489	clear : function (oContext) {
2490		oContext = oContext || this.getContext();
2491		
2492		// workaround: samsung galaxy s3 LTE 4.0.4, LG Optimus G pro 4.1.1에서 딱 맞춰서 clear하면 잔상이 생기거나 오류가 생김		
2493		if (!this._htDeviceInfo.android || (this._htDeviceInfo.android < 4.12 && this._htDeviceInfo.android >= 4.2)) {
2494			oContext.clearRect(0, 0, this._elCanvas.width + 1, this._elCanvas.height + 1);
2495		} else {
2496			// but 4.1.2 still has problem with the clearRect method
2497			this._elCanvas.width = this._elCanvas.width;
2498		}
2499	},
2500	
2501	/**
2502	 * 캔버스 리사이즈
2503	 * 
2504	 * @param {Number} nWidth
2505	 * @param {Number} nHeight
2506	 * @param {Boolean} bExpand
2507	 */
2508	resize : function (nWidth, nHeight, bExpand) {
2509		var htSize = this._getLayerSize(nWidth, nHeight);
2510
2511		if (bExpand) {
2512			this._elCanvas.style.width = nWidth + "px";
2513			this._elCanvas.style.height = nHeight + "px";
2514			var nRatioWidth = nWidth / this._oLayer.option("width");
2515			var nRatioHeight = nHeight / this._oLayer.option("height");			
2516			this._oEvent.setEventRatio(nRatioWidth, nRatioHeight);
2517		} else {
2518			var nCanvasWidth = typeof nWidth === 'number' ? htSize.width : this._elCanvas.width;
2519			var nCanvasHeight = typeof nHeight === 'number' ? htSize.height : this._elCanvas.height;
2520			this.clear(this._oContext);
2521			this._oLayer.setChanged();
2522			this._elCanvas.width = nCanvasWidth;
2523			this._elCanvas.height = nCanvasHeight;
2524			
2525			if (collie.Renderer.isRetinaDisplay()) {
2526				this._elCanvas.style.width = nCanvasWidth / 2 + "px";
2527				this._elCanvas.style.height = nCanvasHeight / 2 + "px";
2528			}
2529		}
2530	}
2531});
2532/**
2533 * DOM 방식의 렌더링
2534 * 
2535 * @private
2536 * @class collie.LayerDOM
2537 * @param {collie.Layer} oLayer
2538 */
2539collie.LayerDOM = collie.Class(/** @lends collie.LayerDOM.prototype */{
2540	/**
2541	 * @private
2542	 * @constructs
2543	 */
2544	$init : function (oLayer) {
2545		this._oLayer = oLayer;
2546		this._oEvent = oLayer.getEvent();
2547		this._htOption = oLayer.option();
2548		this._initElement();
2549		this._rxDisplayObjectId = new RegExp(collie.DisplayObjectDOM.ID + '([0-9]+)');
2550	},
2551	
2552	_initElement : function () {
2553		this._el = document.createElement("div");
2554		this._el.className = "_collie_layer";
2555		this._el.style.position = "absolute";
2556		this._el.style.left = this._htOption.x + "px";
2557		this._el.style.top = this._htOption.y + "px";
2558		this._el.style.width = this._htOption.width + "px";
2559		this._el.style.height = this._htOption.height + "px";
2560	},
2561	
2562	/**
2563	 * 부모를 탐색하면서 표시 객체 엘리먼트를 찾는다
2564	 * 
2565	 * @deprecated
2566	 * @private
2567	 * @param {HTMLElement} el
2568	 * @return {HTMLElement|Boolean}
2569	 */
2570	findDisplayObjectElement : function (el) {
2571		while (el && el.nodeType == 1) {
2572			if (this.isDisplayObjectElement(el) && el.parentNode === this._el) {
2573				return el;
2574			}
2575			
2576			el = el.parentNode;
2577		}
2578		
2579		return false;
2580	},
2581	
2582	/**
2583	 * 표시 객체 엘리먼트인 경우
2584	 * 
2585	 * @deprecated
2586	 * @private
2587	 * @param {HTMLElement} el
2588	 * @return {Boolean} 표시 객체 엘리먼트일 때 true
2589	 */
2590	isDisplayObjectElement : function (el) {
2591		if ("classList" in el) {
2592			return el.classList.contains(collie.DisplayObjectDOM.CLASSNAME);
2593		} else {
2594			return (" " + el.className + " ").indexOf(" " + collie.DisplayObjectDOM.CLASSNAME + " ") > -1;
2595		}
2596	},
2597	
2598	/**
2599	 * 현재 레이어 엘리먼트를 반환
2600	 * 
2601	 * @private
2602	 * @return {HTMLElement}
2603	 */
2604	getElement : function () {
2605		return this._el;
2606	},
2607	
2608	/**
2609	 * 화면을 갱신
2610	 * @private
2611	 */
2612	clear : function () {
2613		return true;		
2614	},
2615	
2616	/**
2617	 * 리사이즈
2618	 * @private
2619	 * @param {Number} nWidth 너비
2620	 * @param {Number} nHeight 높이
2621	 * @param {Boolean} bExpand 확장 여부
2622	 */
2623	resize : function (nWidth, nHeight, bExpand) {
2624		if (bExpand) {
2625			var nRatioWidth = nWidth / this._oLayer.option("width");
2626			var nRatioHeight = nHeight / this._oLayer.option("height");
2627			this._oEvent.setEventRatio(nRatioWidth, nRatioHeight);
2628			this._el.style[collie.util.getCSSPrefix("transform-origin", true)] = "0 0";
2629			
2630			if (collie.util.getSupportCSS3d()) {
2631				this._el.style[collie.util.getCSSPrefix("transform", true)] = "scale3d(" + nRatioWidth + ", " + nRatioHeight + ", 1)";
2632			} else if (collie.util.getSupportCSS3()) {
2633				this._el.style[collie.util.getCSSPrefix("transform", true)] = "scale(" + nRatioWidth + ", " + nRatioHeight + ")";
2634			} else {
2635				// support IE, This method is very heavy.
2636				this._el.style.filter = "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand',M11=" + nRatioWidth + ",M12=0,M21=0,M22=" + nRatioHeight + ");";
2637			}
2638		} else {
2639			this._el.style.width = nWidth + 'px';
2640			this._el.style.height = nHeight + 'px';
2641		}
2642	}
2643});
2644/**
2645 * 레이어 이벤트 처리
2646 * 
2647 * @class collie.LayerEvent
2648 * @param {collie.Layer} oLayer
2649 */
2650collie.LayerEvent = collie.Class(/** @lends collie.LayerEvent.prototype */{
2651	/**
2652	 * 클릭 탐지 값 (px)
2653	 * TODO Androind에서는 반응이 느껴질 수 있으므로 수치를 크게 하는 것이 좋다. (약 12정도?)
2654	 * @constant
2655	 */
2656	THRESHOLD_CLICK : 7,
2657
2658	/**
2659	 * @constructs
2660	 */
2661	$init : function (oLayer) {
2662		this._oLayer = oLayer;
2663		this._bHasTouchEvent = !!('ontouchstart' in window);
2664		this._fOnEvent = this._onEvent.bind(this);
2665		this._oMousedownObject = null;
2666		this._htEventRatio = {
2667			width : 1,
2668			height : 1
2669		};
2670		this._bAttached = false;
2671	},
2672	
2673	/**
2674	 * @private
2675	 */
2676	attachEvent : function () {
2677		var el = this._oLayer.getParent();
2678		
2679		if (this._bHasTouchEvent) {
2680			collie.util.addEventListener(el, "touchstart", this._fOnEvent);
2681			collie.util.addEventListener(el, "touchend", this._fOnEvent);
2682			collie.util.addEventListener(el, "touchmove", this._fOnEvent);
2683			collie.util.addEventListener(el, "touchcancel", this._fOnEvent);
2684		} else {
2685			collie.util.addEventListener(el, "mousedown", this._fOnEvent);
2686			collie.util.addEventListener(el, "mouseup", this._fOnEvent);
2687			collie.util.addEventListener(el, "mousemove", this._fOnEvent);
2688		}
2689		
2690		this._bAttached = true;
2691	},
2692	
2693	/**
2694	 * @private
2695	 */
2696	detachEvent : function () {
2697		var el = this._oLayer.getParent();
2698		
2699		if (this._bAttached) {
2700			if (this._bHasTouchEvent) {
2701				collie.util.removeEventListener(el, "touchstart", this._fOnEvent);
2702				collie.util.removeEventListener(el, "touchend", this._fOnEvent);
2703				collie.util.removeEventListener(el, "touchmove", this._fOnEvent);
2704				collie.util.removeEventListener(el, "touchcancel", this._fOnEvent);
2705			} else {
2706				collie.util.removeEventListener(el, "mousedown", this._fOnEvent);
2707				collie.util.removeEventListener(el, "mouseup", this._fOnEvent);
2708				collie.util.removeEventListener(el, "mousemove", this._fOnEvent);
2709			}
2710			
2711			this._bAttached = false;
2712		}
2713	},
2714	
2715	/**
2716	 * 이벤트 핸들러
2717	 * @private
2718	 * @param {HTMLEvent} e
2719	 */
2720	_onEvent : function (e) {
2721		// 이벤트를 사용하지 않으면 무시
2722		if (!this._oLayer._htOption.useEvent) {
2723			return;
2724		}
2725		
2726		e = e || window.event;
2727		var oEvent = this._bHasTouchEvent ? e.changedTouches[0] : e || window.event;
2728		var el =  this._bHasTouchEvent ? this._getEventTargetElement(e) : e.target || e.srcElement;
2729		var oDocument = el.ownerDocument || document;
2730		var oBody = oDocument.body || oDocument.documentElement;
2731		var nPageX = this._bHasTouchEvent ? oEvent.pageX : oEvent.pageX || oEvent.clientX + oBody.scrollLeft - oDocument.body.clientLeft;
2732		var nPageY = this._bHasTouchEvent ? oEvent.pageY : oEvent.pageY || oEvent.clientY + oBody.scrollTop - oDocument.body.clientTop;
2733		var sType = e.type;
2734		var oDisplayObject = null;
2735		
2736		// 이벤트가 일어난 곳의 상대 좌표를 계산
2737		var htPosition = this._oLayer.getParentPosition();
2738		var nRelatedX = nPageX - htPosition.x - this._oLayer._htOption.x;
2739		var nRelatedY = nPageY - htPosition.y - this._oLayer._htOption.y;
2740		nRelatedX = nRelatedX / this._htEventRatio.width;		
2741		nRelatedY = nRelatedY / this._htEventRatio.height;		
2742		
2743		if (sType === "touchcancel") {
2744			if (this._htEventStartPos !== null) {
2745				nRelatedX = this._htEventStartPos.x;
2746				nRelatedY = this._htEventStartPos.y;
2747			}
2748		}
2749		
2750		sType = this._convertEventType(sType);
2751		
2752		// 기본 액션을 멈춘다(isPreventDefault 상태일 때만)
2753		if (sType === "mousemove" || sType === "mousedown") {
2754			if (collie.Renderer.isPreventDefault()) {
2755				collie.util.stopEventDefault(e);
2756			}
2757		}
2758		
2759		// 좌표 기록
2760		//@TODO 객체 재 사용 해야 함
2761		if (sType === "mousedown") {
2762			this._htEventStartPos = {
2763				x : nRelatedX,
2764				y : nRelatedY
2765			};
2766		}
2767
2768		// Layer 표현 방식대로 이벤트를 발생한다
2769		var bFiredEventOnTarget = this._fireEvent(e, sType, nRelatedX, nRelatedY);
2770		
2771		// 클릭 처리
2772		if (sType === "mouseup") {
2773			// 탐지 영역도 resize에 맞춰서 변경한다
2774			var nThresholdX = this.THRESHOLD_CLICK;
2775			var nThresholdY = this.THRESHOLD_CLICK;
2776			
2777			if (
2778				this._htEventStartPos &&
2779				this._htEventStartPos.x - nThresholdX <= nRelatedX &&
2780				nRelatedX <= this._htEventStartPos.x + nThresholdX &&
2781				this._htEventStartPos.y - nThresholdY <= nRelatedY &&
2782				nRelatedY <= this._htEventStartPos.y + nThresholdY
2783				) {
2784				this._fireEvent(e, "click", nRelatedX, nRelatedY);
2785			}
2786			
2787			this._htEventStartPos = null;
2788		}
2789		
2790		// 이벤트 상태를 저장해서 다른 레이어에 표시객체 이벤트가 통과되지 않도록 방어한다
2791		collie.Renderer.setEventStatus(sType, bFiredEventOnTarget);
2792	},
2793	
2794	/**
2795	 * 레이어에서 이벤트가 일어났을 때 표시 객체에 이벤트를 발생 시킨다
2796	 * 
2797	 * @param {Object} e 이벤트 원본
2798	 * @param {String} sType 이벤트 타입, mouse 이벤트로 변형되서 들어온다
2799	 * @param {Number} nX 이벤트가 일어난 상대좌표
2800	 * @param {Number} nY 이벤트가 일어난 상대좌표
2801	 * @return {Boolean} 표시 객체에 이벤트가 발생했는지 여부 
2802	 * @private
2803	 */
2804	_fireEvent : function (e, sType, nX, nY) {
2805		var oDisplayObject = null;
2806		var bIsNotStoppedBubbling = true;
2807		
2808		// 캔버스에서 이전 레이어에 객체에 이벤트가 일어났으면 다음 레이어의 객체에 전달되지 않는다
2809		if (sType !== "mousemove" && !collie.Renderer.isStopEvent(sType)) {
2810			var aDisplayObjects = this._oLayer.getChildren();
2811			oDisplayObject = this._getTargetOnHitEvent(aDisplayObjects, nX, nY);
2812			
2813			// mousedown일 경우 객체를 저장한다
2814			if (oDisplayObject) {
2815				bIsNotStoppedBubbling = this._bubbleEvent(oDisplayObject, sType, e, nX, nY);
2816				
2817				if (sType === "mousedown") {
2818					this._setMousedownObject(oDisplayObject);
2819				}
2820				if (sType === "mouseup") {
2821					this._unsetMousedownObject(oDisplayObject);
2822				}
2823			}
2824		}
2825		
2826		// mouseup 처리가 안된 경우 임의 발생
2827		if (sType === "mouseup" && this._getMousedownObject() !== null) {
2828			oDisplayObject = this._getMousedownObject();
2829			this._bubbleEvent(oDisplayObject, sType, e, nX, nY);
2830			this._unsetMousedownObject(oDisplayObject);
2831		}
2832		
2833		/**
2834		 * click 이벤트, 모바일 환경일 때는 touchstart, touchend를 비교해서 좌표가 일정 이내로 움직였을 경우 click 이벤트를 발생한다d
2835		 * @name collie.Layer#click
2836		 * @event
2837		 * @param {Object} htEvent
2838		 * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
2839		 * @param {HTMLEvent} htEvent.event 이벤트 객체
2840		 * @param {Number} htEvent.x 상대 x좌표
2841		 * @param {Number} htEvent.y 상대 y좌표
2842		 */
2843		/**
2844		 * mousedown 이벤트, 모바일 환경일 때는 touchstart 이벤트도 해당 된다.
2845		 * @name collie.Layer#mousedown
2846		 * @event
2847		 * @param {Object} htEvent
2848		 * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
2849		 * @param {HTMLEvent} htEvent.event 이벤트 객체
2850		 * @param {Number} htEvent.x 상대 x좌표
2851		 * @param {Number} htEvent.y 상대 y좌표
2852		 */
2853		/**
2854		 * mouseup 이벤트, 모바일 환경일 때는 touchend 이벤트도 해당 된다.
2855		 * @name collie.Layer#mouseup
2856		 * @event
2857		 * @param {Object} htEvent
2858		 * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
2859		 * @param {HTMLEvent} htEvent.event 이벤트 객체
2860		 * @param {Number} htEvent.x 상대 x좌표
2861		 * @param {Number} htEvent.y 상대 y좌표
2862		 */
2863		/**
2864		 * mousemove 이벤트, 모바일 환경일 때는 touchmove 이벤트도 해당 된다.
2865		 * @name collie.Layer#mouseup
2866		 * @event
2867		 * @param {Object} htEvent
2868		 * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
2869		 * @param {HTMLEvent} htEvent.event 이벤트 객체
2870		 * @param {Number} htEvent.x 상대 x좌표
2871		 * @param {Number} htEvent.y 상대 y좌표
2872		 */
2873		if (bIsNotStoppedBubbling) { // stop되면 Layer이벤트도 일어나지 않는다
2874			this._oLayer.fireEvent(sType, {
2875				event : e,
2876				displayObject : oDisplayObject,
2877				x : nX,
2878				y : nY
2879			});
2880		}
2881		
2882		return !!oDisplayObject;
2883	},
2884	
2885	/**
2886	 * 이벤트 대상을 고른다
2887	 * - 가장 위에 있는 대상이 선정되어야 한다
2888	 * @private
2889	 * @param {Array|collie.DisplayObject} vDisplayObject
2890	 * @param {Number} nX 이벤트 상대 x 좌표
2891	 * @param {Number} nY 이벤트 상대 y 좌표
2892	 * @return {collie.DisplayObject|Boolean}
2893	 */
2894	_getTargetOnHitEvent : function (vDisplayObject, nX, nY) {
2895		var oTargetObject = null;
2896		
2897		if (vDisplayObject instanceof Array) {
2898			for (var i = vDisplayObject.length - 1; i >= 0; i--) {
2899				// 자식부터
2900				if (vDisplayObject[i].hasChild()) {
2901					oTargetObject = this._getTargetOnHitEvent(vDisplayObject[i].getChildren(), nX, nY);
2902					
2903					// 찾았으면 멈춤
2904					if (oTargetObject) {
2905						return oTargetObject;
2906					}
2907				}
2908
2909				// 본인도
2910				oTargetObject = this._getTargetOnHitEvent(vDisplayObject[i], nX, nY);
2911				
2912				// 찾았으면 멈춤
2913				if (oTargetObject) {
2914					return oTargetObject;
2915				}
2916			}
2917		} else {
2918			return this._isPointInDisplayObjectBoundary(vDisplayObject, nX, nY) ? vDisplayObject : false;
2919		}
2920	},
2921	
2922	/**
2923	 * 이벤트명 보정
2924	 * 
2925	 * @private
2926	 * @param {String} sType 이벤트 타입
2927	 * @return {String} 변환된 이벤트 타입
2928	 */
2929	_convertEventType : function (sType) {
2930		var sConvertedType = sType;
2931		
2932		switch (sType) {
2933			case "touchstart" :
2934				sConvertedType = "mousedown";
2935				break;
2936				
2937			case "touchmove" :
2938				sConvertedType = "mousemove";
2939				break;
2940				
2941			case "touchend" :
2942			case "touchcancel" :
2943				sConvertedType = "mouseup";
2944				break;
2945				
2946			case "tap" :
2947				sConvertedType = "click";
2948				break;
2949		}
2950		
2951		return sConvertedType;
2952	},
2953	
2954	_getEventTargetElement : function (e) {
2955		var el = e.target;
2956		
2957		while (el.nodeType != 1) {
2958			el = el.parentNode;
2959		}
2960		
2961		return el;
2962	},
2963	
2964	/**
2965	 * 이벤트 대상의 이벤트를 버블링으로 처리 한다
2966	 * 
2967	 * @private
2968	 * @param {collie.DisplayObject} oDisplayObject 버블링 대상
2969	 * @param {String} sType 이벤트명
2970	 * @param {HTMLEvent} e
2971	 * @param {Number} nX 이벤트 상대 x 좌표
2972	 * @param {Number} nY 이벤트 상대 y 좌표
2973	 * @param {collie.DisplayObject} oCurrentObject 이벤트 대상
2974	 * @return {Boolean} 이벤트가 도중에 멈췄으면 false를 반환
2975	 */
2976	_bubbleEvent : function (oDisplayObject, sType, e, nX, nY, oCurrentObject) {
2977		/**
2978		 * click 이벤트, 모바일 환경일 때는 touchstart, touchend를 비교해서 좌표가 일정 이내로 움직였을 경우 click 이벤트를 발생한다d
2979		 * @name collie.DisplayObject#click
2980		 * @event
2981		 * @param {Object} htEvent
2982		 * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
2983		 * @param {HTMLEvent} htEvent.event 이벤트 객체
2984		 * @param {Number} htEvent.x 이벤트 상대 x 좌표
2985		 * @param {Number} htEvent.y 이벤트 상대 y 좌표
2986		 */
2987		/**
2988		 * mousedown 이벤트, 모바일 환경일 때는 touchstart 이벤트도 해당 된다.
2989		 * @name collie.DisplayObject#mousedown
2990		 * @event
2991		 * @param {Object} htEvent
2992		 * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
2993		 * @param {HTMLEvent} htEvent.event 이벤트 객체
2994		 * @param {Number} htEvent.x 이벤트 상대 x 좌표
2995		 * @param {Number} htEvent.y 이벤트 상대 y 좌표
2996		 */
2997		/**
2998		 * mouseup 이벤트, 모바일 환경일 때는 touchend 이벤트도 해당 된다.
2999		 * @name collie.DisplayObject#mouseup
3000		 * @event
3001		 * @param {Object} htEvent
3002		 * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
3003		 * @param {Event} htEvent.event 이벤트 객체
3004		 * @param {HTMLEvent} htEvent.event 이벤트 객체
3005		 * @param {Number} htEvent.x 이벤트 상대 x 좌표
3006		 * @param {Number} htEvent.y 이벤트 상대 y 좌표
3007		 */
3008		if (oDisplayObject.fireEvent(sType, { // stop() 하게 되면 버블링 멈춘다
3009			displayObject : oCurrentObject || oDisplayObject,
3010			event : e,
3011			x : nX,
3012			y : nY
3013		}) === false) {
3014			return false;
3015		}
3016		
3017		// 부모에 이벤트가 전달된다
3018		if (oDisplayObject.getParent() && !this._bubbleEvent(oDisplayObject.getParent(), sType, e, nX, nY, oDisplayObject)) {
3019			return false;
3020		}
3021		
3022		return true;
3023	},
3024	
3025	/**
3026	 * DisplayObject 범위 안에 PointX, PointY가 들어가는지 확인
3027	 * 
3028	 * @private
3029	 * @param {collie.DisplayObject} oDisplayObject
3030	 * @param {Number} nPointX 확인할 포인트 X 좌표
3031	 * @param {Number} nPointY 확인할 포인트 Y 좌표
3032	 * @return {Boolean} 들어간다면 true
3033	 */
3034	_isPointInDisplayObjectBoundary : function (oDisplayObject, nPointX, nPointY) {
3035		// 안보이는 상태거나 이벤트를 받지 않는다면 지나감
3036		if (
3037			!oDisplayObject._htOption.useEvent ||
3038			!oDisplayObject._htOption.visible ||
3039			!oDisplayObject._htOption.width ||
3040			!oDisplayObject._htOption.height ||
3041			(oDisplayObject._htOption.useEvent === "auto" && !oDisplayObject.hasAttachedHandler())
3042			) {
3043			return false;
3044		}
3045		
3046		var htHitArea = oDisplayObject.getHitAreaBoundary();
3047		
3048		// 영역 안에 들어왔을 경우
3049		if (
3050			htHitArea.left <= nPointX && nPointX <= htHitArea.right &&
3051			htHitArea.top <= nPointY && nPointY <= htHitArea.bottom
3052		) {
3053			// hitArea 설정이 없으면 사각 영역으로 체크
3054			if (!oDisplayObject._htOption.hitArea) {
3055				return true;
3056			} else {
3057				var htPos = oDisplayObject.getRelatedPosition();
3058				
3059				// 대상 Point를 상대 좌표로 변경
3060				nPointX -= htPos.x;
3061				nPointY -= htPos.y;
3062				
3063				// transform 적용
3064				var aHitArea = oDisplayObject._htOption.hitArea;
3065				aHitArea = collie.Transform.points(oDisplayObject, aHitArea);
3066				return this._isPointInPolygon(aHitArea, nPointX, nPointY);
3067			}
3068		}
3069		
3070		return false;
3071	},
3072	
3073	/**
3074	 * 대상 Point가 폴리곤 안에 있는지 여부를 반환
3075	 *
3076	 * @private
3077	 * @param {Array} 꼭지점 모음 [[x1, y1], [x2, y2], ... ]
3078	 * @param {Number} nX 대상 점 x 좌표
3079	 * @param {Number} nY 대상 점 y 좌표
3080	 * @return {Boolean} true면 안에 있음
3081	 */
3082	_isPointInPolygon : function (aVertices, nX, nY) {
3083		var bIntersects = false;
3084		
3085		for (var i = 0, j = aVertices.length - 1, len = aVertices.length; i < len; j = i++) {
3086    		if (
3087    			(aVertices[i][1] > nY) !== (aVertices[j][1] > nY) &&
3088	 			(nX < (aVertices[j][0] - aVertices[i][0]) * (nY - aVertices[i][1]) / (aVertices[j][1] - aVertices[i][1]) + aVertices[i][0])
3089	 		) {
3090		       bIntersects = !bIntersects;
3091	 		}
3092		}
3093		
3094		return bIntersects;
3095	},
3096	
3097	/**
3098	 * mousedown 객체를 설정 한다
3099	 * 이 객체를 설정하면 mouseup 이벤트가 나왔을 때 해당 객체에서 하지 않더라도 해당 객체에 mouseup을 일으켜준다
3100	 * @param {collie.DisplayObject} oDisplayObject
3101	 * @private
3102	 */
3103	_setMousedownObject : function (oDisplayObject) {
3104		this._oMousedownObject = oDisplayObject;
3105	},
3106	
3107	/**
3108	 * 지정된 mousedown 객체를 해제 한다. 같은 객체여야만 해제 된다
3109	 * @private
3110	 */
3111	_unsetMousedownObject : function (oDisplayObject) {
3112		if (this._oMousedownObject === oDisplayObject) {
3113			this._oMousedownObject = null;
3114		}
3115	},
3116	
3117	/**
3118	 * mousedown 객체를 반환 한다
3119	 * @private
3120	 * @return {collie.DisplayObject}
3121	 */
3122	_getMousedownObject : function () {
3123		return this._oMousedownObject;
3124	},
3125	
3126	/**
3127	 * 이벤트 좌표 보정치를 설정 한다
3128	 * 
3129	 * @param {Number} nWidth
3130	 * @param {Number} nHeight
3131	 */
3132	setEventRatio : function (nWidth, nHeight) {
3133		this._htEventRatio.width = nWidth || this._htEventRatio.width;
3134		this._htEventRatio.height = nHeight || this._htEventRatio.height;
3135	},
3136	
3137	/**
3138	 * 이벤트 좌표 보정치가 있다면 반환 한다
3139	 * @private
3140	 * @deprecated
3141	 * @return {Object} htEventRatio
3142	 * @return {Number} htEventRatio.width
3143	 * @return {Number} htEventRatio.height
3144	 */
3145	getEventRatio : function () {
3146		return this._htEventRatio;
3147	}
3148});
3149/**
3150 * 표시 객체를 담고 있는 레이어, Stage 개념
3151 * @class collie.Layer
3152 * @extends collie.Component
3153 * @param {Object} [htOption]
3154 * @param {Number} [htOption.width=320] 너비 (px)
3155 * @param {Number} [htOption.height=480] 높이 (px)
3156 * @param {Number} [htOption.x=0] x좌표 (px)
3157 * @param {Number} [htOption.y=0] y좌표 (px)
3158 * @param {Boolean} [htOption.useEvent=true] 이벤트를 사용한다. 속도를 위해 현재 레이어에서 이벤트를 사용하지 않을 수 있다
3159 * @param {Boolean} [htOption.visible=true] 화면 표시 여부
3160 * @param {Boolean} [htOption.freeze=false] true로 설정하면 해당 레이어를 업데이트를 하지 않는다
3161 * @param {Boolean} [htOption.renderingMode=inherit] 레이어 별로 렌더링 모드를 설정 함 [inherit|dom|canvas]
3162 */
3163collie.Layer = collie.Class(/** @lends collie.Layer.prototype */{
3164	/**
3165	 * 클래스 타입
3166	 * @type {String}
3167	 */
3168	type : "layer",
3169	
3170	/**
3171	 * @constructs
3172	 */
3173	$init : function (htOption) {
3174		this.option({
3175			x : 0,
3176			y : 0,
3177			width : 320, // 너비
3178			height : 480, // 높이
3179			useEvent : true,
3180			visible : true,
3181			freeze : false,
3182			renderingMode : "inherit"
3183		});
3184		
3185		// 정렬을 해야한다면 일단 0으로 만들어 놓고 load될 때 정렬함
3186		this._sAlignLeft = null;
3187		this._sAlignTop = null;
3188		
3189		
3190		if (htOption !== undefined) {
3191			if (("x" in htOption) && (htOption.x === "left" || htOption.x === "right" || htOption.x === "center")) {
3192				this._sAlignLeft = htOption.x;
3193				htOption.x = 0;
3194			}
3195			 
3196			if (("y" in htOption) && (htOption.y === "top" || htOption.y === "bottom" || htOption.y === "center")) {
3197				this._sAlignTop = htOption.y;
3198				htOption.y = 0;
3199			}
3200			
3201			this.option(htOption);
3202		}
3203		
3204		this._renderingMode = this._htOption.renderingMode === "inherit" ? collie.Renderer.getRenderingMode() : this._htOption.renderingMode;
3205		
3206		if (this._renderingMode === "canvas" && !collie.util.getDeviceInfo().supportCanvas) {
3207			this._renderingMode = "dom";
3208		}
3209		
3210		this.drawCount = 0; // debugging 용 draw count
3211		this.optionSetter("visible", this._setVisible.bind(this)); // 처음 set은 Drawing이 생성된 후에 실행 된다
3212		this._elParent = null;
3213		this._bChanged = false;
3214		this._aDisplayObjects = [];
3215		this._bLoaded = false;
3216		this._oEvent = new collie.LayerEvent(this);
3217		this._makeDrawing();
3218		this._setVisible();
3219	},
3220	
3221	/**
3222	 * 렌더링 방법을 선택해서 Drawing 객체를 생성 한다
3223	 * @private
3224	 */
3225	_makeDrawing : function () {
3226		this._oDrawing = this._renderingMode === "dom" ? new collie.LayerDOM(this) : new collie.LayerCanvas(this);
3227	},
3228	
3229	/**
3230	 * 드로잉 객체를 반환 한다
3231	 * @return {collie.LayerCanvas|collie.LayerDOM}
3232	 */
3233	getDrawing : function () {
3234		return this._oDrawing;
3235	},
3236	
3237	/**
3238	 * 현재 레이어의 렌더링 모드를 반환
3239	 * 
3240	 * @return {String} [dom|canvas]
3241	 */
3242	getRenderingMode : function () {
3243		return this._renderingMode;
3244	},
3245	
3246	/**
3247	 * 이벤트 객체를 반환 한다
3248	 * @return {collie.LayerEvent}
3249	 */
3250	getEvent : function () {
3251		return this._oEvent;
3252	},
3253	
3254	/**
3255	 * 부모를 반환
3256	 * 
3257	 * @return {HTMLElement}
3258	 */
3259	getParent : function () {
3260		return this._elParent || false;
3261	},
3262	
3263	/**
3264	 * 컨테이너에 엘리먼트 추가. 렌더러에서 load할 때 실행 된다
3265	 * - 로드할 때 가장 큰 레이어를 기준으로 컨테이너의 크기를 정함
3266	 * 
3267	 * @private
3268	 * @param {HTMLElement} elParent
3269	 * @param {Number} nZIndex
3270	 */
3271	load : function (elParent, nZIndex) {
3272		this.unload();
3273		this._bLoaded = true;
3274		this._elParent = this._elParent || elParent;
3275		this._elParent.style.width = Math.max(parseInt(this._elParent.style.width || 0, 10), this.option("width")) + "px";
3276		this._elParent.style.height = Math.max(parseInt(this._elParent.style.height || 0, 10), this.option("height")) + "px";
3277		this.getElement().style.zIndex = nZIndex;
3278		
3279		// 생성자 옵션에 정렬이 포함돼 있으면 load, unload를 반복하더라도 정렬을 계속한다.
3280		// 하지만 사용자가 직접 offset을 사용하는 경우에는 reset되도록 세 번째 인자를 통해 조치한다.
3281		if (this._sAlignLeft !== null) {
3282			this.offset(this._sAlignLeft, null, true);
3283		}
3284		
3285		if (this._sAlignTop !== null) {
3286			this.offset(null, this._sAlignTop, true);
3287		}
3288		
3289		this._elParent.appendChild(this.getElement());
3290	},
3291	
3292	/**
3293	 * @private
3294	 */
3295	unload : function () {
3296		if (this.isLoaded()) {
3297			this._oEvent.detachEvent();
3298			this._elParent.removeChild(this.getElement());
3299			this._elParent = null;
3300			this._bLoaded = false;
3301		}
3302	},
3303	
3304	/**
3305	 * Layer의 attach Event를 순서 조작을 위해 Layer가 하지 않고 Renderer가 한다
3306	 * - 개발용
3307	 * @private
3308	 */
3309	attachEvent : function () {
3310		this._oEvent.attachEvent();
3311	},
3312	
3313	/**
3314	 * Layer의 detach Event를 순서 조작을 위해 Layer가 하지 않고 Renderer가 한다
3315	 * - 개발용
3316	 * @private
3317	 */
3318	detachEvent : function () {
3319		this._oEvent.detachEvent();
3320	},
3321	
3322	/**
3323	 * CSS의 display 속성과 유사
3324	 * @private
3325	 */
3326	_setVisible : function () {
3327		// Drawing이 생성되기 전에 옵션이 설정될 수도 있음
3328		if (this.getElement()) {
3329			this.getElement().style.display = this.option("visible") ? "block" : "none";
3330		}
3331	},
3332	
3333	/**
3334	 * @private
3335	 * @return {Boolean} 로딩 되어있는지 여부
3336	 */
3337	isLoaded : function () {
3338		return this._bLoaded;
3339	},
3340	
3341	/**
3342	 * 자식으로 DisplayObject를 추가한다
3343	 * 
3344	 * @param {collie.DisplayObject} oDisplayObject
3345	 * @param {Boolean} bSkipSort 정렬이나 기타 처리를 스킵한다. private용
3346	 */
3347	addChild : function (oDisplayObject) {
3348		// 추가할 때마다 정렬하기
3349		collie.util.pushWithSort(this._aDisplayObjects, oDisplayObject);
3350		oDisplayObject.setLayer(this);
3351		this.setChanged();
3352	},
3353	
3354	/**
3355	 * 배열로 자식을 여러개 한꺼번에 넣는다. 속도 측면에서 한개씩 넣는 것 보다 이득이다
3356	 * 
3357	 * @param {Array} aList DisplayObject 배열
3358	 */
3359	addChildren : function (aList) {
3360		for (var i = 0, len = aList.length; i < len; i++) {
3361			this.addChild(aList[i]);
3362		}
3363	},
3364	
3365	/**
3366	 * 자식에서 제거한다
3367	 * 
3368	 * @param {collie.DisplayObject} oDisplayObject
3369	 * @param {Number} nIdx 인덱스 번호를 알고 있다면 인덱스 번호를 지정
3370	 */
3371	removeChild : function (oDisplayObject, nIdx) {
3372		oDisplayObject.unsetLayer();
3373		
3374		if (typeof nIdx !== "undefined") {
3375			this._aDisplayObjects.splice(nIdx, 1);
3376		} else {
3377			for (var i = 0, len = this._aDisplayObjects.length; i < len; i++) {
3378				if (this._aDisplayObjects[i] === oDisplayObject) {
3379					this._aDisplayObjects.splice(i, 1);
3380					break;
3381				}
3382			}
3383		}
3384		
3385		this.setChanged();
3386	},
3387	
3388	/**
3389	 * 목록을 받아서 자식에서 제거한다
3390	 *
3391	 * @param {Array} aList
3392	 */
3393	removeChildren : function (aList) {
3394		for (var i = aList.length - 1; i >= 0; i--) {
3395			if (aList[i]) {
3396				this.removeChild(aList[i], i);
3397			}
3398		}
3399	},
3400	
3401	/**
3402	 * 렌더러에 레이어를 추가한다 (DisplayObject와 유사)
3403	 * @TODO 추후 collie.Renderer가 다중 인스턴스가 될 경우 파라미터를 넣읋 수 있음
3404	 * @example before
3405	 * var layer = new collie.Layer();
3406	 * collie.Renderer.addLayer(layer);
3407	 * @example after
3408	 * var layer = new collie.Layer().addTo();
3409	 * 
3410	 * @param {collie.Renderer} [oRenderer] 추가될 렌더러를 지정, 없으면 collie.Renderer를 기본으로 한다
3411	 * @return {collie.Layer} 자기 자신을 반환
3412	 */
3413	addTo : function (oRenderer) {
3414		oRenderer = oRenderer || collie.Renderer;
3415		oRenderer.addLayer(this);
3416		return this;
3417	},
3418	
3419	/**
3420	 * zIndex가 변경되었다면 이 메소드를 호출
3421	 * 
3422	 * @private
3423	 * @param {collie.DisplayObject} oDisplayObject
3424	 */
3425	changeDisplayObjectZIndex : function (oDisplayObject) {
3426		this.removeChild(oDisplayObject);
3427		this.addChild(oDisplayObject);
3428	},
3429	
3430	/**
3431	 * 자식을 반환 한다
3432	 * 
3433	 * @return {Array}
3434	 */
3435	getChildren : function () {
3436		return this._aDisplayObjects;
3437	},
3438	
3439	/**
3440	 * 자식이 있는지 여부를 반환
3441	 * 
3442	 * @return {Boolean} true면 자식이 있음
3443	 */
3444	hasChild : function () {
3445		return this._aDisplayObjects && this._aDisplayObjects.length > 0;
3446	},
3447	
3448	/**
3449	 * 변경된 사항이 있을 경우 DisplayObject에서 Layer에 setChanged를 해서 알린다. setChange된 레이어만 그리기 대상
3450	 * 
3451	 * @private
3452	 */
3453	setChanged : function () {
3454		this._bChanged = true;
3455	},
3456	
3457	/**
3458	 * 변경된 내용이 있는지 여부를 반환
3459	 * 
3460	 * @return {Boolean} true면 변경된 점 있음
3461	 */
3462	isChanged : function () {
3463		return this._bChanged;
3464	},
3465	
3466	/**
3467	 * 변경되지 않은 상태로 되돌린다
3468	 * 
3469	 * @private
3470	 */
3471	unsetChanged : function () {
3472		this._bChanged = false;
3473	},
3474	
3475	/**
3476	 * Canvas Context를 반환
3477	 * 
3478	 * @return {Boolean|Object}
3479	 */
3480	getContext : function () {
3481		return ("getContext" in this._oDrawing) ? this._oDrawing.getContext() : false;
3482	},
3483	
3484	/**
3485	 * 레이어의 엘리먼트를 반환
3486	 * @return {HTMLElement}
3487	 */
3488	getElement : function () {
3489		return ("getElement" in this._oDrawing) ? this._oDrawing.getElement() : false;
3490	},
3491	
3492	/**
3493	 * Layer에 등록된 DisplayObject를 업데이트
3494	 * 
3495	 * @private
3496	 * @param {Number} nFrameDuration 진행된 프레임 시간
3497	 */
3498	update : function (nFrameDuration) {
3499		this.drawCount = 0;
3500		
3501		// 바뀐게 없으면 지나감
3502		if (!this.isChanged() || this.option("freeze")) {
3503			return;
3504		}
3505		
3506		this.clear();
3507		this.unsetChanged();
3508		var nWidth = this.option("width");
3509		var nHeight = this.option("height");
3510		
3511		// 등록된 객체 업데이트
3512		for (var i = 0, len = this._aDisplayObjects.length; i < len; i++) {
3513			this._aDisplayObjects[i].update(nFrameDuration, 0, 0, nWidth, nHeight);
3514		}
3515	},
3516	
3517	/**
3518	 * 화면을 지운다. Canvas일 때만 작동
3519	 */
3520	clear : function () {
3521		this._oDrawing.clear();
3522	},
3523	
3524	/**
3525	 * 레이어의 크기를 변경 한다
3526	 * 모든 레이어의 크기를 변경하고 싶은 경우 Renderer의 resize를 이용한다
3527	 * 
3528	 * @param {Number} nWidth 너비
3529	 * @param {Number} nHeight 높이
3530	 * @param {Boolean} bExpand 확장 여부
3531	 * @see collie.Renderer#resize
3532	 */ 
3533	resize : function (nWidth, nHeight, bExpand) {
3534		if (!bExpand) {
3535			this.option("width", nWidth || this._htOption.width);
3536			this.option("height", nHeight || this._htOption.height);
3537		}
3538		
3539		if (this._oDrawing) {
3540			this._oDrawing.resize(nWidth, nHeight, bExpand);
3541		}
3542		
3543		if (this._elParent) {
3544			this._elParent.style.width = Math.max(parseInt(this._elParent.style.width || 0, 10), nWidth || this.option("width")) + "px";
3545			this._elParent.style.height = Math.max(parseInt(this._elParent.style.height || 0, 10), nHeight || this.option("height")) + "px";
3546		}
3547		
3548		/**
3549		 * resize 메서드가 실행될 때 발생
3550		 * @event
3551		 * @name collie.Layer#resize
3552		 */
3553		this.fireEvent("resize");
3554	},
3555	
3556	/**
3557	 * 레이어의 위치를 변경 한다
3558	 * 레이어의 부모의 크기는 등록된 레이어 중 가장 큰 레이어의 크기에 맞ê²
3558Œ 변경된다.
3559	 * 
3560	 * @param {Number|String} [nX] x좌표(px), left, right, center를 입력하면 Renderer의 크기 기준으로 정렬된다. 렌더러의 크기가 변하더라도 자동으로 움직이지 않는다.
3561	 * @param {Number|String} [nY] y좌표(px), top, bottom, center를 입력하면 Renderer의 크기 기준으로 정렬된다. 렌더러의 크기가 변하더라도 자동으로 움직이지 않는다.
3562	 * @param {Boolean} [bSkipResetInitAlign] private용 변수, 직접 쓰지 않는다.
3563	 */
3564	offset : function (nX, nY, bSkipResetInitAlign) {
3565		var el = this.getElement();
3566		
3567		if (typeof nX !== "undefined" && nX !== null) {
3568			switch (nX) {
3569				case "left" :
3570					nX = 0;
3571					break;
3572					
3573				case "right" :
3574					nX = parseInt(this._elParent.style.width, 10) - this._htOption.width;
3575					break;
3576					
3577				case "center" :
3578					nX = parseInt(this._elParent.style.width, 10) / 2 - this._htOption.width / 2;
3579					break;
3580			}
3581			
3582			this.option("x", nX);
3583			el.style.left = nX + "px";
3584			
3585			if (!bSkipResetInitAlign) {
3586				this._sAlignLeft = null;
3587			}
3588		}
3589		
3590		if (typeof nY !== "undefined" && nY !== null) {
3591			switch (nY) {
3592				case "top" :
3593					nY = 0;
3594					break;
3595					
3596				case "bottom" :
3597					nY = parseInt(this._elParent.style.height, 10) - this._htOption.height;
3598					break;
3599					
3600				case "center" :
3601					nY = parseInt(this._elParent.style.height, 10) / 2 - this._htOption.height / 2;
3602					break;
3603			}
3604			
3605			this.option("y", nY);
3606			el.style.top = nY + "px";
3607			
3608			if (!bSkipResetInitAlign) {
3609				this._sAlignTop = null;
3610			}
3611		}
3612	},
3613	
3614	/**
3615	 * 고민 중... 부모를 렌더러가 아닌 다른 엘리먼트에 붙이는 행위
3616	 * 
3617	 * @param {HTMLElement} elParent
3618	 */
3619	setParent : function (elParent) {
3620		if (this._bLoaded) {
3621			this._oEvent.detachEvent();
3622			this._elParent.removeChild(this.getElement());
3623			this._elParent = elParent;
3624			this._elParent.appendChild(this.getElement());
3625			this._oEvent.attachEvent();
3626		} else {
3627			this._elParent = elParent;
3628		}
3629	},
3630	
3631	/**
3632	 * @private
3633	 * @return {Object}
3634	 */
3635	getParentPosition : function () {
3636		if (this._elParent !== null) {
3637			return this._elParent === collie.Renderer.getElement() ? collie.Renderer.getPosition() : collie.util.getPosition(this._elParent);
3638		}
3639	},
3640	
3641	/**
3642	 * 레이어를 복사
3643	 * 
3644	 * @param {Boolean} bRecursive 등록돼 있는 객체까지 복사할지 여부
3645	 * @return {collie.Layer}
3646	 */
3647	clone : function (bRecursive) {
3648		var oLayer = new this.constructor(this._htOption);
3649		
3650		if (bRecursive && this._aDisplayObjects.length) {
3651			for (var i = 0, l = this._aDisplayObjects.length; i < l; i++) {
3652				this._aDisplayObjects[i].clone(true).addTo(oLayer);
3653			}
3654		}
3655		
3656		return oLayer;
3657	}
3658}, collie.Component);
3659/**
3660 * Canvas에 객체를 그릴 경우 사용할 Drawing 클래스
3661 * - 직접 사용되지 않는 클래스
3662 * 
3663 * @private
3664 * @class collie.DisplayObjectCanvas
3665 * @param {collie.DisplayObject} oDisplayObject
3666 */
3667collie.DisplayObjectCanvas = collie.Class(/** @lends collie.DisplayObjectCanvas.prototype */{
3668	/**
3669	 * @private
3670	 * @constructs
3671	 */
3672	$init : function (oDisplayObject) {
3673		this._oDisplayObject = oDisplayObject;
3674		this._bUseCache = false;
3675		this._oDebugHitArea = null;
3676		this._htEvent = {};
3677		this._oLayer = null;
3678		this._htInfo = this._oDisplayObject.get();
3679		this._bIsRetinaDisplay = null;
3680		
3681		// 캐시 사용
3682		if (this._htInfo.useCache) {
3683			this.loadCache();
3684		}
3685	},
3686
3687	/**
3688	 * 캔버스 캐시 사용
3689	 * 
3690	 * @private
3691	 */
3692	loadCache : function () {
3693		if (!this._bUseCache) {
3694			this._bUseCache = true;
3695			this._elCache = document.createElement("canvas");
3696			this._elCache.width = this._htInfo.width;
3697			this._elCache.height = this._htInfo.height;
3698			this._oContextCache = this._elCache.getContext("2d");
3699		}
3700	},
3701	
3702	/**
3703	 * 캔버스 버퍼 해제
3704	 * @private
3705	 */
3706	unloadCache : function () {
3707		if (this._bUseCache) {
3708			this._oContextCache = null;
3709			this._elCache = null;
3710			this._bUseCache = false;
3711		}
3712	},
3713
3714	/**
3715	 * 버퍼를 비움
3716	 * @private
3717	 */
3718	clearCache : function () {
3719		if (this._bUseCache) {
3720			this._oContextCache.clearRect(0, 0, this._elCache.width, this._elCache.height);
3721			this._elCache.width = this._htInfo.width * (this._bIsRetinaDisplay ? 2 : 1);
3722			this._elCache.height = this._htInfo.height * (this._bIsRetinaDisplay ? 2 : 1);
3723		}
3724	},
3725		
3726	/**
3727	 * 이미지를 그린다
3728	 * - 인자는 drawImage의 첫번째만 다른 인자
3729	 * 직접 호출하지 않음
3730	 * @private
3731	 * @param {CanvasRenderingContext2D}
3731 oContext canvas Context
3732	 * @param {Number} sx
3733	 * @param {Number} sy
3734	 * @param {Number} sw
3735	 * @param {Number} sh
3736	 * @param {Number} dx
3737	 * @param {Number} dy
3738	 * @param {Number} dw
3739	 * @param {Number} dh
3740	 */
3741	drawImage : function (oContext, sx, sy, sw, sh, dx, dy, dw, dh) {
3742		var oSource = this._oDisplayObject.getImage();
3743		var nImageWidth = this._oDisplayObject._nImageWidth; //TODO 임시
3744		var nImageHeight = this._oDisplayObject._nImageHeight;
3745		
3746		// 레티나 디스플레이일 경우 두배씩 증가
3747		if (collie.Renderer.isRetinaDisplay()) {
3748			for (i = 1, len = arguments.length; i < len; i++) {
3749				arguments[i] *= 2;
3750			}
3751			
3752			nImageWidth *= 2;
3753			nImageHeight *= 2;
3754		}
3755		
3756		try {
3757			oContext.drawImage(oSource, sx, sy, sw, sh, dx, dy, dw, dh);
3758		} catch (e) {
3759			throw new Error('invalid drawImage value : ' + sx + ',' + sy + ',' + sw + ',' + sh + ',' + dx + ',' + dy + ',' + dw + ',' + dh + ',' + this._oDisplayObject.getImage().src + ', original : ' + this._oDisplayObject.getImage().width + ',' + this._oDisplayObject.getImage().height + ',source : ' + oSource.width + ',' + oSource.height + ', isCached : ' + (this._elImageCached !== null ? 'true' : 'false'));
3760		}
3761	},
3762	
3763	/**
3764	 * Layer에 붙을 때 실행
3765	 * 
3766	 * @private
3767	 */
3768	load : function () {
3769		this._oLayer = this._oDisplayObject.getLayer();
3770		this._oContext = this._oDisplayObject.getLayer().getContext();
3771		this._bIsRetinaDisplay = collie.Renderer.isRetinaDisplay();
3772	},
3773	
3774	/**
3775	 * Layer에서 빠질 때
3776	 * 
3777	 * @private
3778	 */
3779	unload : function () {
3780		this._oLayer = null;
3781		this._oContext = null;
3782	},
3783	
3784	/**
3785	 * 그리기
3786	 * 
3787	 * @param {Number} nFrameDuration 진행된 프레임 시간
3788	 * @param {Number} nX 객체의 절대 x좌표
3789	 * @param {Number} nY 객체의 절대 y좌표
3790	 * @param {Number} nLayerWidth 레이어 너비, update는 tick안에 있는 로직이기 때문에 성능 극대화를 위해 전달
3791	 * @param {Number} nLayerHeight 레이어 높이 
3792	 * @param {Object} oContext Canvas Context, 없으면 기본 Context를 사용. 부모의 버퍼 Context를 물려 받을 때 사용
3793	 * @private
3794	 */
3795	draw : function (nFrameDuration, nX, nY, nLayerWidth, nLayerHeight, oContext) {
3796		var bUseParentContext = oContext ? true : false;
3797		oContext = oContext || this._oContext;
3798		var oTargetContext = this._bUseCache ? this._oContextCache : oContext; 
3799		var oParentContext = oContext;
3800		var htInfo = this._htInfo;
3801		var htDirty = this._oDisplayObject.getDirty();
3802		var htOrigin = this._oDisplayObject.getOrigin();
3803		var nTargetWidth = htInfo.width; 
3804		var nTargetHeight = htInfo.height;
3805		var nOriginX = htOrigin.x;
3806		var nOriginY = htOrigin.y;
3807		var nSavedX = nX;
3808		var nSavedY = nY;
3809		var nRatio = (this._bIsRetinaDisplay ? 2 : 1);
3810		var nSavedXRatio = nX * nRatio;
3811		var nSavedYRatio = nY * nRatio;
3812		var nSavedOpacity = 0;
3813		var bUseTransform = false;
3814		var oTransformContext = oContext;
3815		
3816		// 캐시를 사용 중이면 oContext 값을 자신의 버퍼로 변경
3817		if (htInfo.useCache) {
3818			oContext = this._oContextCache;
3819		}
3820		
3821		// 레티나 디스플레이 대응
3822		if (this._bIsRetinaDisplay) {
3823			nX *= 2;
3824			nY *= 2;
3825			nOriginX *= 2;
3826			nOriginY *= 2;
3827			nTargetWidth *= 2;
3828			nTargetHeight *= 2; 
3829		}
3830		
3831		// transform 값을 써야할 경우에만 사용
3832		if (this._bUseCache || htInfo.scaleX !== 1 || htInfo.scaleY !== 1 || htInfo.angle !== 0 || htInfo.opacity !== 1) {
3833			bUseTransform = true;
3834			
3835			if (this._bUseCache) {
3836				oTransformContext = !bUseParentContext ? this._oContext : oParentContext;
3837			}
3838			
3839			oTransformContext.save();
3840			oTransformContext.translate(nX + nOriginX, nY + nOriginY);
3841		
3842			if (htInfo.opacity !== 1) {
3843				nSavedOpacity = oTransformContext.globalAlpha;
3844				oTransformContext.globalAlpha = oTransformContext.globalAlpha === 0 ? htInfo.opacity : oTransformContext.globalAlpha * htInfo.opacity;
3845			}
3846			
3847			if (htInfo.angle !== 0) {
3848				oTransformContext.rotate(collie.util.toRad(htInfo.angle));
3849			}
3850			
3851			if (htInfo.scaleX !== 1 || htInfo.scaleY !== 1) {
3852				oTransformContext.scale(htInfo.scaleX, htInfo.scaleY);
3853			}
3854			
3855			oTransformContext.translate(-nOriginX, -nOriginY);
3856			nX = nY = 0;
3857		}
3858		
3859		// 이벤트 객체 재사용
3860		this._htEvent.displayObject = this;
3861		this._htEvent.context = oTargetContext;
3862		this._htEvent.x = nX;
3863		this._htEvent.y = nY;
3864		
3865		// 캐시를 사용하지 않거나 변경되었을 때만 처리
3866		if (!this._bUseCache || (this._oDisplayObject.isChanged() && !this._oDisplayObject.isChanged(true))) {
3867			// 캐시 그리기 전에 비워줌
3868			this.clearCache();
3869			
3870			// 배경색 처리
3871			if (htInfo.backgroundColor) {
3872				oTargetContext.fillStyle = htInfo.backgroundColor;
3873				oTargetContext.fillRect(nX, nY, nTargetWidth, nTargetHeight);
3874			}
3875			
3876			if (this._oDisplayObject.getImage()) {
3877				var elSourceImage = this._oDisplayObject.getImage();
3878				var htImageSize = this._oDisplayObject.getImageSize();
3879				
3880				// 반복 처리
3881				if (htInfo.backgroundRepeat && htInfo.backgroundRepeat !== 'no-repeat') {
3882					var nCountWidth = (htInfo.backgroundRepeat === 'repeat' || htInfo.backgroundRepeat === 'repeat-x') ? Math.ceil(htInfo.width / htImageSize.width) : 1;
3883					var nCountHeight = (htInfo.backgroundRepeat === 'repeat' || htInfo.backgroundRepeat === 'repeat-y') ? Math.ceil(htInfo.height / htImageSize.height) : 1;
3884					
3885					// 이미지 반복 처리
3886					if (nCountWidth > 0 || nCountHeight > 0) {
3887						for (var nLeftOffset = 0; nLeftOffset < nCountWidth; nLeftOffset++) {
3888							for (var nTopOffset = 0; nTopOffset < nCountHeight; nTopOffset++) {
3889								var nOffsetX = nLeftOffset * htImageSize.width + htImageSize.width;
3890								var nOffsetY = nTopOffset * htImageSize.height + htImageSize.height;
3891								var nPieceWidth = nOffsetX > htInfo.width ? htImageSize.width - (nOffsetX - htInfo.width) : htImageSize.width;
3892								var nPieceHeight = nOffsetY > htInfo.height ? htImageSize.height - (nOffsetY - htInfo.height) : htImageSize.height;
3893								
3894								this.drawImage(
3895									oTargetContext,
3896									0,
3897									0,
3898									nPieceWidth,
3899									nPieceHeight,
3900									(nX / nRatio) + nLeftOffset * htImageSize.width,
3901									(nY / nRatio) + nTopOffset * htImageSize.height,
3902									nPieceWidth,
3903									nPieceHeight
3904								);
3905							}
3906						}
3907					}
3908				} else {
3909					var nDrawingWidth = Math.min(htImageSize.width, htInfo.width);
3910					var nDrawingHeight = Math.min(htImageSize.height, htInfo.height);
3911					
3912					//TODO 사이트 이펙트 디바이스 테스트 해야 함 1.0.8
3913					this.drawImage(
3914						oTargetContext,
3915						htInfo.offsetX,
3916						htInfo.offsetY,
3917						htInfo.fitImage ? htImageSize.width : nDrawingWidth,
3918						htInfo.fitImage ? htImageSize.height : nDrawingHeight,
3919						nX / nRatio, //TODO floating value 어떻게 해야할까... 처리하면 계단현상 생김
3920						nY / nRatio,
3921						htInfo.fitImage ? htInfo.width : nDrawingWidth,
3922						htInfo.fitImage ? htInfo.height : nDrawingHeight
3923					);
3924				}
3925			}
3926			
3927			/**
3928			 * Delegate
3929			 * @name collie.DisplayObject#onCanvasDraw
3930			 * @event
3931			 * @delegate
3932			 * @param {Object} htEvent
3933			 * @param {collie.DisplayObject} htEvent.displayObject
3934			 * @param {Object} htEvent.context 캔버스 Context 객체
3935			 * @param {Number} htEvent.x 상대 x좌표
3936			 * @param {Number} htEvent.y 상대 y좌표
3937			 */
3938			if ("onCanvasDraw" in this._oDisplayObject) {
3939				this._oDisplayObject.onCanvasDraw(this._htEvent);
3940			}
3941		}
3942		
3943		// hitArea 그리기
3944		if (htInfo.debugHitArea && htInfo.hitArea) {
3945			if (this._oDebugHitArea === null) {
3946				this._oDebugHitArea = new collie.Polyline({
3947					x : 0,
3948					y : 0,
3949					width : htInfo.width,
3950					height : htInfo.height,
3951					strokeColor : htInfo.debugHitArea === true ? "yellow" : htInfo.debugHitArea,
3952					strokeWidth : 3
3953				}).addTo(this._oDisplayObject);
3954				this._oDebugHitArea.setPointData(htInfo.hitArea);
3955			}
3956		}
3957		
3958		// 자식에게 전파
3959		// 부모에게서 Context를 물려 받았거나, 자신이 useCache를 사용하고 있다면 자식에게 Context를 물려줌. 부모의 설정이 우선시 됨
3960		if (this._oDisplayObject.hasChild() && (!htInfo.useCache || (this._oDisplayObject.isChanged() && !this._oDisplayObject.isChanged(true)))) {
3961			var aDisplayObjects = this._oDisplayObject.getChildren();
3962			
3963			for (var i = 0, len = aDisplayObjects.length; i < len; i++) {
3964				aDisplayObjects[i].update(
3965					nFrameDuration,
3966					// 0,
3967					// 0,
3968					// htInfo.useCache ? 0 : nSavedX, // cache를 사용하면 현재 기준으로 절대 좌표를 넘김
3969					// htInfo.useCache ? 0 : nSavedY,
3970					htInfo.useCache || bUseTransform ? 0 : nSavedX, // cache를 사용하면 현재 기준으로 절대 좌표를 넘김
3971					htInfo.useCache || bUseTransform ? 0 : nSavedY,
3972					nLayerWidth,
3973					nLayerHeight,
3974					bUseParentContext || htInfo.useCache ? oContext : null
3975				);
3976				aDisplayObjects[i].unsetChanged();
3977				aDisplayObjects[i]._resetDirty();
3978			}
3979		}
3980		
3981		// 캐시 기능을 사용하면 자식까지 그린 후에 자기를 그림
3982		if (htInfo.useCache) {
3983			// (bUseParentContext ? oParentContext : this._oContext).drawImage(oContext.canvas, nSavedXRatio, nSavedYRatio);
3984			// (bUseParentContext ? oParentContext : this._oContext).drawImage(oContext.canvas, bUseParentContext ? nSavedXRatio : 0, bUseParentContext ? nSavedYRatio : 0);
3985			(bUseParentContext ? oParentContext : this._oContext).drawImage(oContext.canvas, 0, 0);
3986		}
3987		
3988		this._oLayer.drawCount++;
3989
3990		// 원위치
3991		if (bUseTransform) {
3992			oTransformContext.restore();
3993		}
3994	}
3995});
3996/**
3997 * DisplayObject의 DOM 표시 부분
3998 * @todo 갤럭시 넥서스 ICS에서 CSS3d rotate 사용 시 overflow boundary가 잘못되는 문제점이 있어서 그 부분만 css2d로 동작하도록 변경 했지만, 렌더링 속도가 2d, 3d 차이나는 버그가 남아 있음.
3999 * @private
4000 * @class collie.DisplayObjectDOM
4001 * @param {collie.DisplayObject} oDisplayObject
4002 */
4003collie.DisplayObjectDOM = collie.Class(/** @lends collie.DisplayObjectDOM.prototype */{
4004	/**
4005	 * @private
4006	 * @constructs
4007	 */
4008	$init : function (oDisplayObject) {
4009		this._oDisplayObject = oDisplayObject;
4010		this._htInfo = this._oDisplayObject.get();
4011		this._oLayer = null;
4012		this._elImage = null;
4013		this._aTransformValue = [];
4014		this._sTransformValue = null;
4015		this._sTransform = collie.util.getCSSPrefix("transform", true);
4016		this._sOrigin = collie.util.getCSSPrefix("transform-origin", true);
4017		this._bSupportCSS3 = collie.util.getSupportCSS3();
4018		this._bSupportCSS3d = collie.util.getSupportCSS3d();
4019		this._bUseTransform = this._bSupportCSS3 || this._bSupportCSS3d;
4020		this._htDeviceInfo = collie.util.getDeviceInfo();
4021		this._bIsAndroid = !!this._htDeviceInfo.android;
4022		this._nAndroidVersion = this._htDeviceInfo.android;
4023		this._bIsIEUnder8 = this._htDeviceInfo.ie && this._htDeviceInfo.ie < 9;
4024		this._bUseTranslateZ = true;
4025		this._bIsRetinaDisplay = null;
4026		this._htEvent = {};
4027		this._oEmptyObject = {};
4028		this._sCacheTransformValue = null;
4029		this._initElement();
4030	},
4031	
4032	_initElement : function () {
4033		// container
4034		this._elContainer = document.createElement("div");
4035		this._elContainer.id = collie.DisplayObjectDOM.ID + this._oDisplayObject.getId() + (this._htInfo.name ? "_" + this._htInfo.name : "");
4036		this._elContainer.className = collie.DisplayObjectDOM.CLASSNAME;
4037		this._elContainerStyle = this._elContainer.style;
4038		this._elContainerStyle.position = "absolute";
4039		
4040		// IE의 경우 크기가 정해져 있지 않으면 filter가 정상적으로 작동하지 않음
4041		if (this._bIsIEUnder8) {
4042			this._elContainerStyle.width = this._htInfo.width + "px";
4043			this._elContainerStyle.height = this._htInfo.height + "px"; 
4044		}
4045		
4046		// element
4047		this._el = document.createElement("div");
4048		this._elStyle = this._el.style;
4049		
4050		if (this._bSupportCSS3d) {
4051			this._elStyle[this._sTransform] = "translateZ(0)";
4052		}
4053		
4054		this._elStyle.position = "absolute";
4055		this._elStyle.width = this._htInfo.width + "px";
4056		this._elStyle.height = this._htInfo.height + "px";
4057		this._elStyle.overflow = "hidden";
4058		this._elContainer.appendChild(this._el);
4059	},
4060		
4061	load : function () {
4062		this._oLayer = this._oDisplayObject.getLayer();
4063		
4064		// 부모가 있으면 부모 엘리먼트에 직접 붙임
4065		if (this._oDisplayObject.getParent()) {
4066			this._oDisplayObject.getParent().getDrawing().getElement().appendChild(this._elContainer);
4067		} else {
4068			this._oLayer.getElement().appendChild(this._elContainer);
4069		}
4070		
4071		this._bIsRetinaDisplay = collie.Renderer.isRetinaDisplay();
4072	},
4073	
4074	unload : function () {
4075		this._oLayer = null;
4076		this._elContainer.parentNode.removeChild(this._elContainer);
4077	},
4078	
4079	/**
4080	 * 현재 객체의 엘리먼트를 반환
4081	 * 
4082	 * @return {HTMLElement}
4083	 */
4084	getElement : function () {
4085		return this._elContainer;
4086	},
4087	
4088	/**
4089	 * 현재 객체의 아이템 엘리먼트를 반환
4090	 * 
4091	 * @return {HTMLElement}
4092	 */
4093	getItemElement : function () {
4094		return this._el;
4095	},
4096	
4097	/**
4098	 * 그리기
4099	 * 
4100	 * @private
4101	 * @param {Number} nFrameDuration 진행된 프레임 시간
4102	 * @param {Number} nX 객체의 절대 x좌표
4103	 * @param {Number} nY 객체의 절대 y좌표
4104	 * @param {Number} nLayerWidth 레이어 너비, update는 tick안에 있는 로직이기 때문에 성능 극대화를 위해 전달
4105	 * @param {Number} nLayerHeight 레이어 높이
4106	 */
4107	draw : function (nFrameDuration, nX, nY, nLayerWidth, nLayerHeight) {
4108		// 객체 재사용
4109		this._htEvent.displayObject = this;
4110		this._htEvent.element = this._el;
4111		this._htEvent.x = nX;
4112		this._htEvent.y = nY;
4113		
4114		var htInfo = this._htInfo;
4115		var htDirty = this._oDisplayObject.getDirty() || this._oEmptyObject;
4116		var htOrigin = this._oDisplayObject.getOrigin();
4117		var nRatio = (this._bIsRetinaDisplay ? 2 : 1);
4118		
4119		if (htDirty.visible) {
4120			this._elContainerStyle.display = htInfo.visible ? "block" : "none";
4121		}
4122		
4123		if (htDirty.width) {
4124			this._elStyle.width = htInfo.width + "px";
4125			
4126			if (this._bIsIEUnder8) {
4127				this._elContainerStyle.width = htInfo.width + "px";
4128			}
4129		}
4130		
4131		if (htDirty.height) {
4132			this._elStyle.height = htInfo.height + "px";
4133		}
4134		
4135		if (htDirty.opacity) {
4136			if (this._bIsIEUnder8) {
4137				this._elContainerStyle.filter = "alpha(opacity=" + (htInfo.opacity * 100) + ")";
4138			} else {
4139				this._elContainerStyle.opacity = htInfo.opacity;
4140				
4141				// Androind 4.1 workaround
4142				// TODO Androind 4.2에서 없어졌는지 확인해봐야 함
4143				if (this._elImage && this._nAndroidVersion && this._nAndroidVersion >= 4.1) {
4144					this._elImage.style.opacity = htInfo.opacity;
4145				}
4146			}
4147		}
4148		
4149		if (htDirty.zIndex) {
4150			this._elContainerStyle.zIndex = htInfo.zIndex;
4151		}
4152		
4153		if (htDirty.backgroundColor) {
4154			this._elStyle.backgroundColor = htInfo.backgroundColor;
4155		}
4156		
4157		// 이동
4158		// transform은 여러 항목을 동시에 사용하기 때문에 겹쳐도 계산해야 한다.
4159		// 하지만 직접 style에 접근하는 경우는 변경될 때에만 값에 접근해 reflow를 줄인다
4160		if (this._bUseTransform) {
4161			this._aTransformValue.push(this._makeTranslate(htInfo.x, htInfo.y, htInfo.zIndex));
4162		} else if (htDirty.x || htDirty.y) {
4163			this._elContainerStyle.left = htInfo.x + "px";
4164			this._elContainerStyle.top = htInfo.y + "px";
4165		}
4166		
4167		//48~49
4168		
4169		// origin 적용
4170		if (this._bUseTransform) {
4171			if (htDirty.originX || htDirty.originY || htDirty.width || htDirty.height) {
4172				this._elContainerStyle[this._sOrigin] = htOrigin.x + "px " + htOrigin.y + "px";
4173			}
4174			
4175			if (htInfo.angle !== 0) {
4176				this._aTransformValue.push("rotate", (this._bSupportCSS3d && !this._bIsAndroid ? "Z" : ""), "(", htInfo.angle, "deg) ");
4177			}
4178			
4179			// scale이 translate보다 나중에 되야 한다
4180			if (htInfo.scaleX !== 1 || htInfo.scaleY !== 1) {
4181				this._aTransformValue.push("scale(", htInfo.scaleX, ", ", htInfo.scaleY, ") ");
4182			}
4183		}
4184		
4185		//46~47
4186		if (this._bUseTransform) {
4187			this._applyTransform();
4188		}
4189		
4190		//24
4191		this._drawImage(htInfo, htDirty);
4192		
4193		/**
4194		 * Delegate
4195		 * @name collie.DisplayObject#onDOMDraw
4196		 * @delegate
4197		 * @event
4198		 * @param {Object} htEvent
4199		 * @param {collie.DisplayObject} htEvent.displayObject
4200		 * @param {HTMLElement} htEvent.element 현재 엘리먼트
4201		 * @param {Number} htEvent.x 객체의 절대 x 좌표
4202		 * @param {Number} htEvent.y 객체의 절대 y 좌표
4203		 */
4204		if ("onDOMDraw" in this._oDisplayObject) {
4205			this._oDisplayObject.onDOMDraw(this._htEvent);
4206		}
4207		
4208		this._oLayer.drawCount++;
4209	},
4210	
4211	/**
4212	 * 이미지와 관련된 작업을 수행
4213	 * 
4214	 * @private
4215	 */
4216	_drawImage : function (htInfo, htDirty) {
4217		var elSourceImage = this._oDisplayObject.getImage();
4218		var bUseRepeat = htInfo.backgroundRepeat && htInfo.backgroundRepeat !== 'no-repeat';
4219		var nImageWidth = 0;
4220		var nImageHeight = 0;
4221		
4222		// 이미지 늘리기
4223		if (this._oDisplayObject._htOption.fitImage) {
4224			nImageWidth = this._oDisplayObject._htOption.width;
4225			nImageHeight = this._oDisplayObject._htOption.height;
4226		} else {
4227			var htImageSize = this._oDisplayObject.getImageSize();
4228			nImageWidth = htImageSize.width;
4229			nImageHeight = htImageSize.height;
4230		}
4231		
4232		// CSSText를 쓰면 Dirty는 빼야 한다
4233		if (htDirty.backgroundImage || htDirty.backgroundRepeat) {
4234			
4235			// android trasnform에서 엘리먼트를 지우면 깜빡거림, 최대한 재사용 함
4236			if (this._elImage !== null && (!htInfo.backgroundImage || (htInfo.backgroundRepeat && htInfo.backgroundRepeat !== "no-repeat"))) {
4237			// if (this._elImage !== null) {
4238				this._el.removeChild(this._elImage);
4239				this._elImage = null;
4240			}
4241			
4242			if (htInfo.backgroundImage && elSourceImage) {
4243				if (!bUseRepeat && htInfo.backgroundImage) {
4244					var elImageStyle;
4245					
4246					if (this._elImage === null) {
4247						// android 3d trasnforms 버그 때문에 div로 감쌈
4248						this._elImage = elSourceImage.cloneNode();
4249						elImageStyle = this._elImage.style;
4250						elImageStyle.position = "absolute";
4251						elImageStyle.top = 0;
4252						elImageStyle.left = 0;
4253						elImageStyle.width = nImageWidth + "px";
4254						elImageStyle.height = nImageHeight + "px";
4255						elImageStyle.visibility = "visible";
4256						
4257						// Androind 4.1 workaround
4258						// TODO Androind 4.2에서 없어졌는지 확인해봐야 함
4259						if (this._nAndroidVersion && this._nAndroidVersion >= 4.1) {
4260							elImageStyle.opacity = htInfo.opacity;
4261						}
4262						
4263						if (this._bSupportCSS3d && this._bUseTranslateZ) {
4264							elImageStyle[this._sTransform] = "translateZ(0)";
4265						}
4266						
4267						if (this._el.hasChildNodes()) {
4268							this._el.insertBefore(this._elImage, this._el.childNodes[0]);
4269						} else {
4270							this._el.appendChild(this._elImage);
4271						}
4272					} else {
4273						this._elImage.src = elSourceImage.src;
4274						elImageStyle = this._elImage.style;
4275						elImageStyle.width = nImageWidth + "px";
4276						elImageStyle.height = nImageHeight + "px";
4277						elImageStyle.visibility = "visible";
4278					}
4279				} else if (bUseRepeat) {
4280					this._elStyle.backgroundImage = 'url("' + elSourceImage.src + '")';				
4281					this._elStyle.backgroundRepeat = htInfo.backgroundRepeat;
4282				}
4283			}
4284		}
4285		
4286		if (htInfo.backgroundImage && this._elImage !== null) {
4287			// 레티나 이미지 처리
4288			if (this._bIsRetinaDisplay && bUseRepeat && (htDirty.width || htDirth.height || htDirty.backgroundRepeat || htDirty.backgroundImage)) {
4289				this._elStyle.backgroundSize = htInfo.width + "px " + htInfo.height + "px";
4290			}
4291			
4292			if (htDirty.offsetX || htDirty.offsetY) {
4293				if (this._bUseTransform) {
4294					this._elImage.style[this._sTransform] = this._makeTranslate(-htInfo.offsetX, -htInfo.offsetY, 1);
4295				} else {
4296					this._elImage.style.left = -htInfo.offsetX + "px";
4297					this._elImage.style.top = -htInfo.offsetY + "px";
4298				}
4299			}
4300		}
4301	},
4302		
4303	/**
4304	 * translate 구문 생성, 2d, 3d를 구분해서 생성한다
4305	 * 
4306	 * @private
4307	 * @param {Number} nX
4308	 * @param {Number} nY
4309	 * @param {Number} nZ
4310	 * @return {String} transform에 입력할 translate 값
4311	 */
4312	_makeTranslate : function (nX, nY, nZ) {
4313		var sTranslate = '';
4314		var bUseCSS3d = (this._htInfo.angle !== 0 && this._bIsAndroid) ? false : this._bSupp
4314ortCSS3d;
4315		// getAngle
4316		
4317		if (bUseCSS3d) {
4318			sTranslate = "translate3d(" + nX + "px, " + nY + "px, " + nZ + "px) ";
4319		} else if (this._bSupportCSS3) {
4320			sTranslate = "translate(" + nX + "px, " + nY + "px) ";
4321		}
4322		
4323		return sTranslate;
4324	},
4325	
4326	/**
4327	 * 누적된 transform 속성을 적용한다
4328	 * @private
4329	 */
4330	_applyTransform : function () {
4331		var sValue = this._aTransformValue.join("");
4332		
4333		if (this._sCacheTransformValue !== sValue) {
4334			this._elContainerStyle[this._sTransform] = sValue;
4335			this._sCacheTransformValue = sValue;
4336			
4337			if (this._bIsAndroid && this._bUseTranslateZ && this._htInfo.angle !== 0) {
4338				this._elStyle[this._sTransform] = "";
4339				
4340				if (this._elImage) {
4341					this._oDisplayObject.setChanged();
4342				}
4343				
4344				this._bUseTranslateZ = false;
4345			} else if (!this._bUseTranslateZ && (this._htInfo.angle === 0 || !this._bIsAndroid) && this._bSupportCSS3d) {
4346				this._elStyle[this._sTransform] = "translateZ(0)";
4347				
4348				if (this._elImage) {
4349					this._oDisplayObject.setChanged();
4350				}
4351				
4352				this._bUseTranslateZ = true;
4353			}
4354		}
4355		
4356		this._aTransformValue = [];
4357	}
4358}, collie.Component);
4359
4360/**
4361 * 엘리먼트 클래스 이름
4362 * @constants
4363 * @memberOf collie.DisplayObjectDOM
4364 */
4365collie.DisplayObjectDOM.CLASSNAME = "_collie_displayObject";
4366		
4367/**
4368 * 엘리먼트 아이디 prefixt
4369 * @constants
4370 * @memberOf collie.DisplayObjecDOM
4371 */
4372collie.DisplayObjectDOM.ID = "_collie_displayObject_";
4373/**
4374 * - A DisplayObject is basic class for display object.
4375 * - A DisplayObject can have one or many displayObject like a DOM Element.
4376 * - A offset values changed by when you set a spriteX and spriteY options, but there is no change when you set offsetX and offsetY values.
4377 * - You can use the addTo method with the method chaining
4378 * - A DisplayObject should be set useCache option as true if it doesn't change frequently.
4379 * 
4380 * @class collie.DisplayObject
4381 * @extends collie.Component
4382 * @param {Object} [htOption] Options
4383 * @param {String} [htOption.name] 객체 이름, 중복 가능
4384 * @param {Number|String} [htOption.width="auto"] 너비, auto 값일 경우 backgroundImage가 설정되면 해당 이미지 너비 만큼 자동으로 변경 된다
4385 * @param {Number|String} [htOption.height="auto"] 높이, auto 값일 경우 backgroundImage가 설정되면 해당 이미지 높이 만큼 자동으로 변경 된다
4386 * @param {Number|String} [htOption.x=0] x축 위치, left, right, center 값을 사용하면 부모를 기준으로 정렬
4387 * @param {Number|String} [htOption.y=0] y축 위치, top, bottom, center 값을 사용하면 부모를 기준으로 정렬
4388 * @param {Number} [htOption.zIndex=0] 표시 순서. 높을 수록 위에 있음. 같으면 추가한 순서대로
4389 * @param {Number} [htOption.opacity=1] 투명도(0~1)
4390 * @param {Number} [htOption.angle=0] 회전각(0~360 deg)
4391 * @param {Number} [htOption.scaleX=1] x축 비율
4392 * @param {Number} [htOption.scaleY=1] y축 비율
4393 * @param {Number} [htOption.originX=center] scale, angle 적용 가로 기준 [left, right, center, 숫자]
4394 * @param {Number} [htOption.originY=center] scale, angle 적용 세로 기준 [top, bottom, center, 숫자]
4395 * @param {Number} [htOption.offsetX=0] 배경 이미지 시작 x좌표
4396 * @param {Number} [htOption.offsetY=0] 배경 이미지 시작 y좌표
4397 * @param {Number} [htOption.spriteX=null] 배경 가로 스프라이트 index, 너비 * index 값으로 offsetX가 설정된다
4398 * @param {Number} [htOption.spriteY=null] 배경 세로 스프라이트 index, 높이 * index 값으로 offsetY가 설정된다
4399 * @param {Number} [htOption.spriteLength=0] 배경 스프라이트 frame수, 가로폭 제한 스프라이트일 경우에 전체 프레임 수를 지정한다. 높이가 height보다 크지 않은 경우 적용되지 않는다
4400 * @param {Number} [htOption.spriteSheet=null] 배경 스프라이트 시트 이름, ImageManager에서 addSprite로 정보를 넣었을 경우 사용할 수 있다
4401 * @param {Array} [htOption.rangeX=null] X좌표 가용 범위. 배열로 최소, 최대값을 설정 [min, max], 상대 좌표임(현재 객체의 x, y좌표와 동일)
4402 * @param {Array} [htOption.rangeY=null] Y좌표 가용 범위. 배열로 최소, 최대값을 설정 [min, max], 상대 좌표임(현재 객체의 x, y좌표와 동일)
4403 * @param {Boolean} [htOption.positionRepeat=false] x,y 좌표의 범위 설정(rangeX, rangeY)이 되어 있는 경우 범위를 벗어나면 원점으로 돌아오는지 여부를 ì
4403„¤ì •. fasleë©´ 경계값까지만 움직이고 멈춤
4404 * @param {String} [htOption.backgroundColor] 배경색
4405 * @param {String} [htOption.backgroundImage] 배경이미지, 이미지매니져 리소스 이름이나 엘리먼트
4406 * @param {String} [htOption.backgroundRepeat='no-repeat'] 배경 이미지 반복 방법 repeat, repeat-x, repeat-y, no-repeat, no-repeat이 아니라면 useCache가 자동으로 true로 변함
4407 * @param {Boolean} [htOption.fitImage=false] 이미지를 객체 크기에 맞추기 
4408 * @param {collie.DisplayObject|Array} [htOption.hitArea] 이벤트 영역으로 사용될 다른 객체나 Polyline Path를 배열로 설정한다. 이 때 좌표는 상대 좌표 [[x1, y1], [x2, y2], ...] 
4409 * @param {Boolean} [htOption.debugHitArea=false] 이벤트 영역으로 사용된 hitArea를 화면에서 직접 확인할 수 있다. 성능에 좋지 않기 때문에 디버깅할 때만 사용해야 한다. 
4410 * @param {Number} [htOption.velocityX=0] x축 속도(초당 px)
4411 * @param {Number} [htOption.velocityY=0] y축 속도(초당 px)
4412 * @param {Number} [htOption.velocityRotate=0] 회전 속도(초당 1도)
4413 * @param {Number} [htOption.forceX=0] x축 힘(초당 px)
4414 * @param {Number} [htOption.forceY=0] y축 힘(초당 px)
4415 * @param {Number} [htOption.forceRotate=0] 회전 힘(초당 1도)
4416 * @param {Number} [htOption.mass=1] 질량
4417 * @param {Number} [htOption.friction=0] 마찰력
4418 * @param {Boolean} [htOption.useRealTime=true] SkippedFrame을 적용해서 싸이클을 현재 시간과 일치 
4419 * @param {Boolean} [htOption.useCache=false] 타일 캐시 사용 여부. 자식 객체를 모두 자신의 Context에 그려 놓는다.
4420 * @param {String|Boolean} [htOption.useEvent="auto"] 이벤트 사용 여부, Layer옵션과 DisplayObject 옵션 중에 하나라도 false라면 동작하지 않는다. auto면 attach된 이벤트가 있을 경우에만 동작한다 
4421 * @param {Boolean} [htOption.visible=true] 화면 표시 여부. false면 자식 객체도 보이지 않는다. "hidden" 값으로 설정하면 자식 객체는 표시하고 자신만 보이지 않게 한다
4422 */
4423collie.DisplayObject = collie.Class(/** @lends collie.DisplayObject.prototype */{
4424	/**
4425	 * 클래스 타입
4426	 * @type {String}
4427	 */
4428	type : "displayobject",
4429
4430	/**
4431	 * @constructs
4432	 */
4433	$init : function (htOption) {
4434		this._bInitOption = true;
4435		this._htOption = {
4436			name : "", // 객체 이름
4437			width : 'auto',
4438			height : 'auto',
4439			x : 0,
4440			y : 0,
4441			zIndex : 0, // 표시 순서
4442			opacity : 1, // 투명도
4443			angle : 0, // 로테이션(각도)
4444			scaleX : 1,
4445			scaleY : 1,
4446			originX : "center",
4447			originY : "center",
4448			offsetX : 0,
4449			offsetY : 0,
4450			spriteX : null,
4451			spriteY : null,
4452			spriteLength : 0,
4453			spriteSheet : null,
4454			rangeX : null, // X좌표 가용 범위를 설정, 배열로 min, max 설정함 [min, max]
4455			rangeY : null, // Y좌표 가용 범위를 설정, 배열로 min, max 설정함 [min, max]
4456			positionRepeat : false, // x,y 좌표의 범위 설정이 되어 있는 경우 범위를 벗어나면 원점으로 돌아오는지 여부를 설정. fasle면 경계값까지만 움직이고 멈춤
4457			backgroundColor : '', // 배경색
4458			backgroundImage : '', // 배경이미지, 이미지매니져 리소스 이름이나 엘리먼트
4459			backgroundRepeat : 'no-repeat', // 배경 이미지 반복 repeat, repeat-x, repeat-y, no-repeat
4460			hitArea : null,
4461			debugHitArea : false,
4462			useCache : false,
4463			useEvent : "auto",
4464			fitImage : false, // 이미지를 객체 크기에 맞게 늘리기
4465			velocityX : 0,
4466			velocityY : 0,
4467			velocityRotate : 0,
4468			forceX : 0,
4469			forceY : 0,
4470			forceRotate : 0,
4471			mass : 1, // 질량
4472			friction : 0, // 마찰
4473			useRealTime : true,
4474			visible : true // 화면 표시 여부
4475		};
4476		
4477		if (htOption) {
4478			this.option(htOption);
4479		}
4480		
4481		this._htDirty = {};
4482		this._htMatrix = {};
4483		this._sId = ++collie.DisplayObject._idx;
4484		this._elImage = null;
4485		this._aDisplayObjects = [];
4486		this._oLayer = null;
4487		this._oParent = null;
4488		this._oDrawing = null;
4489		this._bIsSetOption = false;
4490		this._bChanged = true;
4491		this._bChangedTransforms = true;
4492		this._bCustomSize = false;
4493		this._aChangedQueue = null;
4494		this._htGetImageData = null;
4495		this._htRelatedPosition = {};
4496		this._htParentRelatedPosition = {};
4497		this._htBoundary = {};
4498		this._sRenderingMode = null;
4499		this._bRetinaDisplay = collie.Renderer.isRetinaDisplay();
4500		this._oTimerMove = null;
4501		this._nPositionRight = null;
4502		this._nPositionBottom = null;
4503		this._nImageWidth = 0;
4504		this._nImageHeight = 0;
4505		this._htImageSize = null;
4506		this._htSpriteSheet = null;
4507		this._htOrigin = {
4508			x : 0,
4509			y : 0
4510		};
4511		
4512		this.set(this._htOption);
4513		this._bIsSetOption = true;
4514	},
4515	
4516	/**
4517	 * 설정 값을 변경한다
4518	 * @example
4519	 * oDisplayObject.set({
4520	 * 	visible : false,
4521	 *  opacity : 1
4522	 * });
4523	 * 
4524	 * oDisplayObject.set("visible", true);
4525	 * 
4526	 * @param {String|Object} vKey 설정 이름. 여러개의 값을 Object로 한번에 설정할 수 있다.
4527	 * @param {Variables} vValue 값
4528	 * @param {Boolean} [bSkipSetter] setter를 수행하지 않음. 일반적으로 사용하는 것은 권장하지 않는다
4529	 * @param {Boolean} [bSkipChanged] 상태 변경을 하지 않는다. 상태변경을 하지 않게 되면 다시 그리지 않는다
4530	 * @return {collie.DisplayObject} For Method Chaining
4531	 */
4532	set : function (vKey, vValue, bSkipSetter, bSkipChanged) {
4533		if (typeof vKey === "object") {
4534			// 나머지 실행
4535			for (var i in vKey) {
4536				this.set(i, vKey[i]);
4537			}
4538		} else {
4539			// 값이 변하지 않았다면 처리하지 않음
4540			if (this._bIsSetOption && this._htOption[vKey] === vValue) {
4541				return;
4542			}
4543			
4544			// 크기 자동 변경 값 적용
4545			if (vKey === "width" || vKey === "height") {
4546				if (vValue !== "auto") {
4547					this._bCustomSize = true;
4548				} else if (vValue === "auto" && this.getImage() !== null) {
4549					vValue = this.getImageSize()[vKey];
4550				} else {
4551					vValue = 100;
4552				}
4553			}
4554			
4555			this._htOption[vKey] = vValue;
4556			this.setDirty(vKey); // record value to find
4557			
4558			if (!bSkipSetter) {
4559				this._setter(vKey, vValue);
4560			}
4561			
4562			if (!bSkipChanged) {
4563				// check if DisplayObject changed only transform values
4564				this.setChanged((vKey === 'x' || vKey === 'y' || vKey === 'angle' || vKey === 'scaleX' || vKey === 'scaleY' || vKey === 'opacity') ? true : false);
4565			}
4566		}
4567		
4568		return this;
4569	},
4570	
4571	/**
4572	 * setter
4573	 * @private
4574	 * @param {String} vKey 설정 이름
4575	 * @param {Variables} vValue 값
4576	 */
4577	_setter : function (vKey, vValue) {
4578		// zIndex hash 갱신
4579		if (vKey === "zIndex") {
4580			if (this._oParent) {
4581				this._oParent.changeDisplayObjectZIndex(this);
4582			} else if (this.getLayer()) {
4583				this._oLayer.changeDisplayObjectZIndex(this);
4584			}
4585		}
4586		
4587		// 값 보정
4588		if (vKey === "x" || vKey === "y") {
4589			if (typeof vValue === "string") {
4590				this.align(vKey === "x" ? vValue : false, vKey === "y" ? vValue : false);
4591			}
4592			
4593			this._fixPosition();
4594			this.resetPositionCache();
4595		}
4596		
4597		// 이미지 설정
4598		if (vKey === "backgroundImage") {
4599			this.setImage(vValue);
4600		}
4601		
4602		// 스프라이트 속성 적용
4603		if (vKey === 'spriteX' || vKey === 'spriteY' || vKey === 'spriteSheet') {
4604			this._setSpritePosition(vKey, vValue);
4605		}
4606		
4607		// hitArea 배열 캐싱
4608		if (vKey === 'hitArea' && vValue instanceof Array) {
4609			this._makeHitAreaBoundary();
4610		}
4611		
4612		// origin 변환
4613		if (vKey === 'width' || vKey === 'height' || vKey === 'originX' || vKey === 'originY') {
4614			this._setOrigin();
4615		}
4616		
4617		// 배경 반복 상태면 캐시 사용
4618		if ((vKey === 'backgroundRepeat' && vValue !== 'no-repeat')) {
4619			this.set("useCache", true);
4620		}
4621		
4622		// 캔버스 캐시 생성
4623		if (vKey === 'useCache' && this._oDrawing !== null && this._oDrawing.loadCache) {
4624			if (vValue) {
4625				this._oDrawing.loadCache();
4626			} else {
4627				this._oDrawing.unloadCache();
4628			}
4629		}
4630	},
4631	
4632	/**
4633	 * 설정 값을 가져온다
4634	 * @param {String} sKey 값이 없으면 전체 값을 반환
4635	 * @return {Variable|Object} 설정 값
4636	 * @example
4637	 * var htData = oDisplayObject.get();
4638	 * var bVisible = oDisplayObject.get("visible");
4639	 * @example
4640	 * <caption>성능을 올리기 위해서는 메서드 호출을 최소한으로 줄이는 것이 좋다
4641	 * If you want to improve performance to your service, you should use less method call.</caption>
4642	 * // before
4643	 * var x = oDisplayObject.get("x");
4644	 * var y = oDisplayObject.get("y");
4645	 * var width = oDisplayObject.get("width");
4646	 * var height = oDisplayObject.get("height");
4647	 * 
4648	 * // after
4649	 * var htInfo = oDisplayObject.get();
4650	 * htInfo.x;
4651	 * htInfo.y;
4652	 * htInfo.width;
4653	 * htInfo.height;
4654	 * 
4655	 * // or you can access a htOption object directly. It's not recommend but It's better than frequently method call.
4656	 * oDisplayObject._htOption.x;
4657	 * oDisplayObject._htOption.y;
4658	 * oDisplayObject._htOption.width;
4659	 * oDisplayObject._htOption.height;
4660	 */
4661	get : function (sKey) {
4662		if (!sKey) {
4663			return this._htOption;
4664		} else {
4665			return this._htOption[sKey];
4666		}
4667	},
4668	
4669	/**
4670	 * 값이 변경된 것으로 설정
4671	 * 
4672	 * @param {String} sKey 키 이름, 값이 없으면 모든 값을 다시 적용함
4673	 */
4674	setDirty : function (sKey) {
4675		if (this._htDirty === null) {
4676			this._htDirty = {};
4677		}
4678		
4679		if (typeof sKey === "undefined") {
4680			for (var i in this._htOption) {
4681				this._htDirty[i] = true; 				
4682			}
4683		} else {
4684			this._htDirty[sKey] = true; 				
4685		}
4686	},
4687	
4688	/**
4689	 * 값이 변경된 것을 알림
4690	 *
4691	 * @param {String} sKey 키 이름
4692	 * @return {Boolean} true면 값이 변경 됐음
4693	 */
4694	getDirty : function (sKey) {
4695		if (!sKey) {
4696			return this._htDirty;
4697		} else {
4698			return this._htDirty[sKey] ? true : false;
4699		}
4700	},
4701	
4702	/**
4703	 * Dirty 값을 초기화, 다 그리고 난 후에 실행 한다
4704	 * @private
4705	 */
4706	_resetDirty : function () {
4707		this._htDirty = null;
4708	},
4709	
4710	/**
4711	 * DisplayObject의 자식을 추가 한다
4712	 * - 자식으로 들어간 DisplayObject는 현재 DisplayObject의 zIndex 영향을 받게 된다
4713	 * 
4714	 * @param {collie.DisplayObject} oDisplayObject
4715	 */
4716	addChild : function (oDisplayObject) {
4717		collie.util.pushWithSort(this._aDisplayObjects, oDisplayObject);
4718		oDisplayObject.setParent(this);
4719		
4720		if (this._oLayer !== null) {
4721			oDisplayObject.setLayer(this._oLayer);
4722		}
4723		
4724		this.setChanged();
4725	},
4726	
4727	/**
4728	 * 자식을 제거 한다
4729	 * @param {collie.DisplayObject} oDisplayObject
4730	 * @param {Number} nIdx 인덱스 번호를 알고 있다면 인덱스 번호를 지정
4731	 */
4732	removeChild : function (oDisplayObject, nIdx) {
4733		if (typeof nIdx !== "undefined") {
4734			this._aDisplayObjects[nIdx].unsetLayer();
4735			this._aDisplayObjects[nIdx].unsetParent();			
4736			this._aDisplayObjects.splice(nIdx, 1);
4737		} else {
4738			for (var i = 0, len = this._aDisplayObjects.length; i < len; i++) {
4739				if (this._aDisplayObjects[i] === oDisplayObject) {
4740					this._aDisplayObjects[i].unsetLayer();
4741					this._aDisplayObjects[i].unsetParent();
4742					this._aDisplayObjects.splice(i, 1);
4743					break;
4744				}
4745			}
4746		}
4747		
4748		this.setChanged();
4749	},
4750	
4751	/**
4752	 * zIndex가 변경되었다면 이 메소드를 호출
4753	 * 
4754	 * @private
4755	 * @param {collie.DisplayObject} oDisplayObject
4756	 */
4757	changeDisplayObjectZIndex : function (oDisplayObject) {
4758		this.removeChild(oDisplayObject);
4759		this.addChild(oDisplayObject);
4760	},
4761	
4762	/**
4763	 * 레이어나 DisplayObject 객체에 현재 객체를 추가 한다.
4764	 * 
4765	 * @param {collie.Layer|collie.DisplayObject} oTarget
4766	 * @return {collie.DisplayObject}
4767	 */
4768	addTo : function (oTarget) {
4769		// 이미 추가돼 있다면 빼고 다시 넣음
4770		if (this._oLayer || this._oParent) {
4771			// 같은데라면 동작 취소
4772			if (this._oLayer === oTarget || this._oParent === oTarget) {
4773				return this;
4774			} else {
4775				this.leave();
4776			}
4777		}
4778		
4779		oTarget.addChild(this);
4780		return this;
4781	},
4782	
4783	/**
4784	 * 자식이 있는지 반환
4785	 * 
4786	 * @return {Boolean} 자식이 있다면 true
4787	 */
4788	hasChild : function () {
4789		return this._aDisplayObjects.length > 0;
4790	},
4791	
4792	/**
4793	 * 자식을 반환
4794	 * 
4795	 * @return {Array}
4796	 */
4797	getChildren : function () {
4798		return this._aDisplayObjects;
4799	},
4800	
4801	/**
4802	 * 부모를 반환
4803	 * 
4804	 * @return {collie.DisplayObject}
4805	 */
4806	getParent : function () {
4807		return this._oParent || false;
4808	},
4809	
4810	/**
4811	 * 부모를 설정
4812	 * - 직접 호출하지 않는다
4813	 * @private
4814	 * @param {collie.DisplayObject} oDisplayObject
4815	 */
4816	setParent : function (oDisplayObject) {
4817		this._oParent = oDisplayObject;	
4818	},
4819	
4820	/**
4821	 * 부모를 해제
4822	 * @private
4823	 */
4824	unsetParent : function () {
4825		this._oParent = null;
4826	},
4827	
4828	/**
4829	 * 부모가 있을 경우 부모에서 자신을 뺀다
4830	 * @return {collie.DisplayObject} 자신을 반환
4831	 */
4832	leave : function () {
4833		var oParent = null;
4834		
4835		if (this._oParent !== null) {
4836			oParent = this._oParent;
4837		} else if (this._oLayer) {
4838			oParent = this.getLayer();
4839		}
4840		
4841		if (oParent) {
4842			oParent.removeChild(this);
4843		}
4844		
4845		return this;
4846	},
4847	
4848	/**
4849	 * 아이디를 반환 한다
4850	 * 
4851	 * @return {String}
4852	 */
4853	getId : function () {
4854		return this._sId;
4855	},
4856	
4857	/**
4858	 * 현재 객체의 배경 이미지를 가져온다
4859	 * 
4860	 * @return {HTMLElement}
4861	 */
4862	getImage : function () {
4863		return this._elImage || null;
4864	},
4865	
4866	/**
4867	 * 이미지 크기를 반환, 레티나일 경우 보정된 값을 반환 한다
4868	 * Return a size of the image set to backgroundImage property.
4869	 * If The User has a retina display, this method would return a half of size.
4870	 * ex) 640*940 -> 320*480
4871	 * 
4872	 * @return {Boolean|Object} htSize 이미지가 로드되지 않았으면 false를 반환. It would return as false when it has not loaded the image yet.
4873	 * @return {Number} htSize.width
4874	 * @return {Number} htSize.height
4875	 */
4876	getImageSize : function () {
4877		return this._htImageSize || false;
4878	},
4879	
4880	/**
4881	 * 이미지를 설정한다
4882	 * - TODO 비동기 주의해야 함
4883	 * - TODO setImage 바로 못하게 해야 함 backgroundImage로... 값이 어긋남
4884	 * @param {String|HTMLImageElement} vImage ImageManager의 리소스 이름이나 이미지 엘리먼트
4885	 * @private
4886	 */
4887	setImage : function (vImage) {
4888		if (typeof vImage === "string" || !vImage) {
4889			// 이미 걸어놓은 이미지가 있다면 취소
4890			if (this._htGetImageData !== null && this._htGetImageData.name !== vImage) {
4891				collie.ImageManager.cancelGetImage(this._htGetImageData.name, this._htGetImageData.callback);
4892				this._htGetImageData = null;
4893			}
4894			
4895			if (!vImage) {
4896				this._elImage = null;
4897				this.setChanged();
4898			} else {
4899				this._htGetImageData = {
4900					name : vImage,
4901					callback : (function (elImage) {
4902						this.setImage(elImage);
4903					}).bind(this)
4904				};
4905				
4906				collie.ImageManager.getImage(this._htGetImageData.name, this._htGetImageData.callback);
4907			}
4908			
4909			return;
4910		}
4911		
4912		// 같은 이미지면 적용하지 않음
4913		if (this._elImage && this._elImage === vImage) {
4914			return;
4915		}
4916		
4917		// reflow 예방을 위한 이미지 크기 캐시
4918		this._elImage = vImage;
4919		this._nImageWidth = vImage.width;
4920		this._nImageHeight = vImage.height;
4921		this._htImageSize = {
4922			width : this._bRetinaDisplay ? this._nImageWidth / 2 : this._nImageWidth,
4923			height : this._bRetinaDisplay ? this._nImageHeight / 2 : this._nImageHeight
4924		};
4925		this._htSpriteSheet = collie.ImageManager.getSprite(this._htOption.backgroundImage);
4926		
4927		// 사용자가 크기를 설정 안했으면 자동으로 이미지 크기로 설정 됨
4928		if (!this._bCustomSize) {
4929			this.set({
4930				width : this._htImageSize.width,
4931				height : this._htImageSize.height
4932			});
4933		}
4934		
4935		this._setSpritePosition("spriteSheet", this._htOption.spriteSheet);
4936		this._setSpritePosition("spriteX", this._htOption.spriteX);
4937		this._setSpritePosition("spriteY", this._htOption.spriteY);
4938		this.setDirty("backgroundImage");
4939		this.setChanged();
4940	},
4941	
4942	/**
4943	 * 드로잉 객체를 반환
4944	 * @return {collie.DisplayObjectCanvas|collie.DisplayObjectDOM}
4945	 */
4946	getDrawing : function () {
4947		return this._oDrawing;
4948	},
4949	
4950	/**
4951	 * 변경된 내용이 있을 경우 Layer에 알린다
4952	 * - 개발용
4953	 * TODO setChanged 실행 횟수가 많은데 중복 실행을 줄이면 성능이 향상되나?
4954	 * -> flag만 두고 실제 setChanged 전파는 draw하기 전에 하는 것임
4955	 * 
4956	 * @private
4957	 * @param {Boolean} bChangedTransforms transform 값이 변경되는지 여부
4958	 */
4959	setChanged : function (bChangedTransforms) {
4960		// 이미 변경된 것으로 돼 있다면 실행하지 않음
4961		if (this._bChanged || (bChangedTransforms && this._bChangedTransforms)) {
4962			return;
4963		}
4964		
4965		if (this._oLayer !== null) {
4966			this._oLayer.setChanged();
4967		}
4968		
4969		if (!bChangedTransforms) {
4970			this._bChanged = true;
4971		}
4972		
4973		this._bChangedTransforms = true;
4974		
4975		// 부모가 있다면 부모도 바뀐 상태로 변경, 반복적으로 부모에게 전달됨
4976		if (this._oParent) {
4977			this._oParent.setChanged(false); // transforms만 바꼈어도 부모에게는 전체가 바뀐것으로 통보
4978		}
4979	},
4980	
4981	/**
4982	 * 변경된 내용이 반영 되었을 때
4983	 * TODO changed라는 이름 변경할 필요성 있음
4984	 * @private
4985	 */
4986	unsetChanged : function () {
4987		this._bChanged = false;
4988		this._bChangedTransforms = false;
4989	},
4990	
4991	/**
4992	 * 현재 객체에 변경된 내용 여부를 반환
4993	 * DOM일 경우 변경된게 없으면 다시 안그림
4994	 * 
4995	 * @param {Boolean} bChangedOnlyTranforms
4996	 * @return {Boolean}
4997	 */
4998	isChanged : function (bChangedOnlyTranforms) {
4999		return !bChangedOnlyTranforms ? (this._bChanged || this._bChangedTransforms) : !this._bChanged && this._bChangedTransforms;
5000	},
5001	
5002	/**
5003	 * 레이어에 객체를 추가
5004	 * 
5005	 * - 직접 사용하지 않는다
5006	 * @private
5007	 * @param {collie.Layer} oLayer
5008	 */
5009	setLayer : function (oLayer) {
5010		// 중복된 값이 있으면 에러
5011		if (this._sId in collie.DisplayObject.htFactory) {
5012			throw new Error('Exists DisplayObject Id ' + this._sId);
5013		}
5014		
5015		collie.DisplayObject.htFactory[this._sId] = this;
5016		this._oLayer = oLayer;
5017		this._sRenderingMode = this._oLayer.getRenderingMode();
5018		this._makeDrawing();
5019		this._oDrawing.load();
5020		this.setChanged();
5021		
5022		// 정렬 적용
5023		if (typeof this._htOption.x === "string" || typeof this._htOption.y === "string") {
5024			this.align(typeof this._htOption.x === "string" ? this._htOption.x : false, typeof this._htOption.y === "string" ? this._htOption.y : false);
5025		}
5026		
5027		if (this._nPositionRight !== null) {
5028			this.right(this._nPositionRight);
5029			this._nPositionRight = null;
5030		}
5031		
5032		if (this._nPositionBottom !== null) {
5033			this.bottom(this._nPositionBottom);
5034			this._nPositionBottom = null;
5035		}
5036		
5037		// 자식도 setLayer 적용
5038		for (var i = 0, len = this._aDisplayObjects.length; i < len; i++) {
5039			this._aDisplayObjects[i].setLayer(oLayer);
5040		}
5041	},
5042
5043	/**
5044	 * 레이어에서 객체를 뺌
5045	 * @private
5046	 */	
5047	unsetLayer : function () {
5048		if (this.getLayer()) {
5049			for (var i = 0, len = this._aDisplayObjects.length; i < len; i++) {
5050				this._aDisplayObjects[i].unsetLayer();
5051			}
5052		
5053			this._oDrawing.unload();
5054			this.setDirty();
5055			this.setChanged();
5056			this._sRenderingMode = null;
5057			this._oDrawing = null;
5058			this._oLayer = null;
5059			delete collie.DisplayObject.htFactory[this._sId];
5060		}
5061	},
5062	
5063	/**
5064	 * @private
5065	 */
5066	_makeDrawing : function () {
5067		if (this._oDrawing === null) {
5068			this._oDrawing = this._sRenderingMode === "dom" ? new collie.DisplayObjectDOM(this) : new collie.DisplayObjectCanvas(this);
5069		}
5070	},
5071	
5072	/**
5073	 * 레이어 반환
5074	 * 
5075	 * @return {collie.Layer|Boolean}
5076	 */
5077	getLayer : function () {
5078		return this._oLayer || false;
5079	},
5080	
5081	/**
5082	 * 다양한 속성을 변경하며 사용할 경우 addMatrix를 이용해 설정을 미리 만들고 changeMatrix로 변경해 사용할 수 있다.
5083	 * 
5084	 * @param {Array|Object} vMatrix 배열로 여러개를 한번에 넣을 수 있음
5085	 * @param {String} vMatrix.name Matrix 이름
5086	 * @param {Number} vMatrix.property 변경할 설정을 입력한다 
5087	 * @example
5088	 * oDisplayObject.addMatrix({
5089	 * 	name : "test"
5090	 * 	offsetX : 0,
5091	 * 	offsetY : 100
5092	 * });
5093	 * oDisplayObject.addMatrix([
5094	 * 	{ name : "test2", offsetX : 100, offsetY : 100, width : 50, height : 50 },
5095	 * 	{ name : "test3", offsetX : 200, offsetY : 100, width : 80, height : 80 }
5096	 * ]);
5097	 * 
5098	 * oDisplayObject.changeMatrix("test2");
5099	 * oDisplayObject.changeMatrix("test3");
5100	 */
5101	addMatrix : function (vMatrix) {
5102		if (vMatrix instanceof Array) {
5103			for (var i = 0, len = vMatrix.length; i < len; i++) {
5104				this.addMatrix(vMatrix[i]);
5105			}
5106			return;
5107		}
5108		
5109		this._htMatrix[vMatrix.name] = vMatrix;
5110		delete this._htMatrix[vMatrix.name].name;
5111	},
5112	
5113	/**
5114	 * 해당 Matrix로 변경한다
5115	 * 
5116	 * @param {String} sName 매트릭스 이름
5117	 */
5118	changeMatrix : function (sName) {
5119		if (sName in this._htMatrix) {
5120			this.set(this._htMatrix[sName]);
5121		}
5122	},
5123	
5124	/**
5125	 * DisplayObject를 갱신한다.
5126	 * 
5127	 * @param {Number} nFrameDuration 진행된 프레임 시간
5128	 * @param {Number} nX 부모로 부터 내려온 x좌표
5129	 * @param {Number} nY 부모로 부터 내려온 y좌표
5130	 * @param {Number} nLayerWidth 레이어 너비, update는 tick안에 있는 로직이기 때문에 성능 극대화를 위해 전달
5131	 * @param {Number} nLayerHeight 레이어 높이
5132	 * @param {Object} oContext 부모의 Canvas Context, useCache를 사용하면 넘어온다
5133	 * @return {Boolean} true를 반환하면 계속 바뀔게 있다는 뜻
5134	 * @private
5135	 */
5136	update : function (nFrameDuration, nX, nY, nLayerWidth, nLayerHeight, oContext) {
5137		this._updateMovableOption(nFrameDuration);
5138		
5139		// Canvas 방식이고, 보이지 않는 객체면 그린걸로 친다, 자식도 그리지 않아도 된다.
5140		if (this._sRenderingMode === "canvas" && !this._htOption.visible) {
5141			this.unsetChanged();
5142			return;
5143		}
5144		
5145		nX += this._htOption.x;
5146		nY += this._htOption.y;
5147		
5148		// Canvas에서 화면 밖으로 나가거나 DOM에서 바뀐게 있을 떄 그림
5149		if (
5150				(this._sRenderingMode === "dom" && this.isChanged()) || (
5151				this._sRenderingMode === "canvas" && (
5152					nX + this._htOption.width >= 0 ||
5153					nX <= nLayerWidth ||
5154					nY + this._htOption.height >= 0 ||
5155					nY <= nLayerHeight
5156				)
5157			)) {
5158			this._oDrawing.draw(nFrameDuration, nX, nY, nLayerWidth, nLayerHeight, oContext);
5159		}
5160		
5161		this.unsetChanged();
5162		this._resetDirty();
5163		
5164		// 움직임이 있으면 다시 바뀐 상태로 둠
5165		if (
5166			this._htOption.velocityX !== 0 ||
5167			this._htOption.velocityY !== 0 ||
5168			this._htOption.velocityRotate !== 0 ||
5169			this._htOption.forceX !== 0 ||
5170			this._htOption.forceY !== 0 ||
5171			this._htOption.forceRotate !== 0
5172		) {
5173			this.setChanged(true);
5174		}
5175		
5176		// Canvas 방식은 자식을 직접 그리고, DOM 방식이면 부모가 보이지 않는 상태면 자식도 그리지 않는다
5177		if (this._sRenderingMode === "canvas" || !this._htOption.visible) {
5178			return;
5179		}
5180		
5181		// update 자식에게 전파
5182		if (this.hasChild()) {
5183			for (var i = 0, len = this._aDisplayObjects.length; i < len; i++) {
5184				this._aDisplayObjects[i].update(nFrameDuration, nX, nY, nLayerWidth, nLayerHeight);
5185			}
5186		}
5187	},
5188	
5189	_updateMovableOption : function (nFrameDuration) {
5190		if (
5191			this._htOption.velocityX !== 0 ||
5192			this._htOption.velocityY !== 0 ||
5193			this._htOption.velocityRotate !== 0 ||
5194			this._htOption.forceX !== 0 ||
5195			this._htOption.forceY !== 0 ||
5196			this._htOption.forceRotate !== 0
5197		) {
5198			var nFrame = Math.max(17, nFrameDuration) / 1000;
5199			
5200			// skippedFrame 적용을 하지 않는다면 1frame 씩만 그림
5201			if (!this._htOption.useRealTime) {
5202				nFrame = 1;
5203			}
5204			
5205			var nVelocityX = this._htOption.velocityX;
5206			var nVelocityY = this._htOption.velocityY;
5207			var nX = this._htOption.x;
5208			var nY = this._htOption.y;
5209			
5210			// 힘 적용 a = F / m
5211			nVelocityX += (this._htOption.forceX / this._htOption.mass) * nFrame;
5212			nVelocityY += (this._htOption.forceY / this._htOption.mass) * nFrame;
5213			
5214			// 마찰력 적용
5215			var nForceFrictionX = this._htOption.friction * nVelocityX * this._htOption.mass * nFrame;
5216			var nForceFrictionY = this._htOption.friction * nVelocityY * this._htOption.mass * nFrame;
5217			
5218			if (nVelocityX !== 0) {
5219				nVelocityX = (Math.abs(nVelocityX) / nVelocityX !== Math.abs(nVelocityX - nForceFrictionX) / (nVelocityX - nForceFrictionX)) ? 0 : nVelocityX - nForceFrictionX;
5220			}
5221			
5222			if (nVelocityY !== 0) {
5223				nVelocityY = (Math.abs(nVelocityY) / nVelocityY !== Math.abs(nVelocityY - nForceFrictionY) / (nVelocityY - nForceFrictionY)) ? 0 : nVelocityY - nForceFrictionY;
5224			}
5225			
5226			nX += nVelocityX * nFrame;
5227			nY += nVelocityY * nFrame;
5228			nVelocityX = Math.floor(nVelocityX * 1000) / 1000;
5229			nVelocityY = Math.floor(nVelocityY * 1000) / 1000;
5230			
5231			if (this._htOption.friction && Math.abs(nVelocityX) < 0.05) {
5232				nVelocityX = 0;
5233			}
5234			
5235			if (this._htOption.friction && Math.abs(nVelocityY) < 0.05) {
5236				nVelocityY = 0;
5237			}
5238		
5239			// 변경이 있을 때만 설정
5240			if (
5241				nX !== this._htOption.x ||
5242				nY !== this._htOption.y ||
5243				nVelocityX !== this._htOption.velocityX ||
5244				nVelocityY !== this._htOption.velocityY
5245			) {
5246				this.set("x", nX);
5247				this.set("y", nY);
5248				this.set("velocityX", nVelocityX);
5249				this.set("velocityY", nVelocityY);
5250			}
5251			
5252			if (this._htOption.forceRotate !== 0) {
5253				this.set("velocityRotate", this._htOption.velocityRotate + this._htOption.forceRotate);
5254			}
5255			
5256			if (this._htOption.velocityRotate !== 0) {
5257				var nAngleRad = collie.util.fixAngle(collie.util.toRad(this._htOption.angle + this._htOption.velocityRotate * nFrame));
5258				this.set("angle", Math.round(collie.util.toDeg(nAngleRad) * 1000) / 1000);
5259			}
5260		}
5261	},
5262	
5263	/**
5264	 * 부모와 연관된 전체 좌표를 구한다(절대좌표)
5265	 * @todo 메소드 명이 직관적이지 못하다
5266	 * 
5267	 * @return {Object} htPos
5268	 * @return {Number} htPos.x
5269	 * @return {Number} htPos.y
5270	 */
5271	getRelatedPosition : function () {
5272		if (this._htRelatedPosition.x === null) {
5273			this._htRelatedPosition.x = this._htOption.x;
5274			this._htRelatedPosition.y = this._htOption.y;
5275			
5276			if (this._oParent) {
5277				var htPosition = this._oParent.getRelatedPosition();
5278				this._htRelatedPosition.x += htPosition.x;
5279				this._htRelatedPosition.y += htPosition.y;
5280			}
5281		}
5282		
5283		return this._htRelatedPosition;
5284	},
5285	
5286	/**
5287	 * 현재 표시 객체의 사각형 영역을 반환 한다
5288	 * - transform된 영역을 반환
5289	 * TODO Transform Matrix의 origin에 상대좌표를 적용해야 하기 때문에 캐시를 적용할 수 없음
5290	 * TODO Transform 안 된지도 부모를 타고 가봐야 알 수 있음!
5291	 * 
5292	 * @param {Boolean} bWithRelatedPosition 절대좌표로 변경해서 반환하는지 여부
5293	 * @param {Boolean} bWithPoints 좌표를 반환하는지 여부, Sensor의 box hittest에서 쓰임
5294	 * @return {Object} oBoundary
5295	 * @return {Number} oBoundary.left
5296	 * @return {Number} oBoundary.right
5297	 * @return {Number} oBoundary.top
5298	 * @return {Number} oBoundary.bottom
5299	 * @return {Number} oBoundary.isTransform 트랜스폼 사용 여부
5300	 * @return {Array} oBoundary.points bWithPoints를 true로 하면 좌표 배열이 넘어옴, [[x, y], [x, y], ...]
5301	 */
5302	getBoundary : function (bWithRelatedPosition, bWithPoints) {
5303		var htBoundary = collie.Transform.getBoundary(this, bWithPoints);
5304		this._htBoundary.left = htBoundary.left;
5305		this._htBoundary.right = htBoundary.right;
5306		this._htBoundary.top = htBoundary.top;
5307		this._htBoundary.bottom = htBoundary.bottom;
5308		this._htBoundary.isTransform = htBoundary.isTransform;
5309		this._htBoundary.points = htBoundary.points;
5310		
5311		// 절대 좌표로 변환해서 반환
5312		if (bWithRelatedPosition) {
5313			var htPos = this.getRelatedPosition();
5314			
5315			if (this._htBoundary.points) {
5316				for (var i = 0, l = this._htBoundary.points.length; i < l; i++) {
5317					this._htBoundary.points[i][0] += htPos.x;
5318					this._htBoundary.points[i][1] += htPos.y;
5319				}
5320			}
5321			
5322			this._htBoundary.left += htPos.x;
5323			this._htBoundary.right += htPos.x;
5324			this._htBoundary.top += htPos.y;
5325			this._htBoundary.bottom += htPos.y;
5326		}
5327		
5328		return this._htBoundary;
5329	},
5330	
5331	/**
5332	 * 위치가 변경되는 경우 캐시를 초기화 해 줌
5333	 * @private
5334	 */
5335	resetPositionCache : function () {
5336		this._htRelatedPosition.x = null;
5337		this._htRelatedPosition.y = null;
5338		
5339		// 자체적으로 전파
5340		// TODO 속도 차이 반드시 확인해 봐야 함!!
5341		if (this.hasChild()) {
5342			for (var i = 0, l = this._aDisplayObjects.length; i < l; i++) {
5343				this._aDisplayObjects[i].resetPositionCache();
5344			}
5345		}
5346	},
5347
5348	/**
5349	 * 이벤트와 관련된 영역을 반환 한다
5350	 * - transform된 영역을 반환
5351	 * - 절대 좌표로 변환해서 반환한다
5352	 * 
5353	 * @return {Object} htReturn
5354	 * @return {Number} htReturn.left minX
5355	 * @return {Number} htReturn.right maxX
5356	 * @return {Number} htReturn.top minY
5357	 * @return {Number} htReturn.bottom maxY
5358	 */
5359	getHitAreaBoundary : function () {
5360		if (!this._htOption.hitArea) {
5361			return this.getBoundary(true);
5362		} else if (this._htOption.hitArea instanceof Array) {
5363			var aPoints = collie.Transform.points(this, collie.util.getBoundaryToPoints(this._htHitAreaBoundary));
5364			var htBoundary = collie.util.getBoundary(aPoints);
5365			var htPos = this.getRelatedPosition();
5366			
5367			return {
5368				left : htBoundary.left + htPos.x,
5369				right : htBoundary.right + htPos.x,
5370				top : htBoundary.top + htPos.y,
5371				bottom : htBoundary.bottom + htPos.y
5372			};
5373		} else { // displayObject일 경우
5374			return this._htOption.hitArea.getBoundary(true);
5375		}
5376	},
5377	
5378	/**
5379	 * Scale, Angle 변경의 중심점을 구한다
5380	 * 
5381	 * @private
5382	 * @return {Object} htResult
5383	 * @return {Number} htResult.x x축 Origin
5384	 * @return {Number} htResult.y y축 Origin
5385	 */
5386	getOrigin : function () {
5387		return this._htOrigin;
5388	},
5389	
5390	/**
5391	 * origin을 px로 설정한다
5392	 * @private
5393	 */
5394	_setOrigin : function () {
5395		switch (this._htOption.originX) {
5396			case "left" :
5397				this._htOrigin.x = 0;
5398				break;
5399				
5400			case "right" :
5401				this._htOrigin.x = this._htOption.width;
5402				break;
5403				
5404			case "center" :
5405				this._htOrigin.x = this._htOption.width / 2;
5406				break;
5407				
5408			default :
5409				this._htOrigin.x = parseInt(this._htOption.originX, 10);
5410		}
5411				
5412		switch (this._htOption.originY) {
5413			case "top" :
5414				this._htOrigin.y = 0;
5415				break;
5416				
5417			case "bottom" :
5418				this._htOrigin.y = this._htOption.height;
5419				break;
5420				
5421			case "center" :
5422				this._htOrigin.y = this._htOption.height / 2;
5423				break;
5424				
5425			default :
5426				this._htOrigin.y = parseInt(this._htOption.originY, 10);
5427		}
5428	},
5429	
5430	/**
5431	 * range를 사용하고 있는 경우 range에 맞게 포지션을 변경 한다
5432	 * 
5433	 * @private
5434	 */
5435	_fixPosition : function () {
5436		var nX = this._htOption.x;
5437		var nY = this._htOption.y;
5438		var nMinX;
5439		var nMaxX;
5440		var nMinY;
5441		var nMaxY;
5442		
5443		if (this._htOption.rangeX) {
5444			// 상대를 절대 값으로
5445			nMinX = this._htOption.rangeX[0];
5446			nMaxX = this._htOption.rangeX[1];
5447			
5448			if (this._htOption.positionRepeat) {
5449				if (nX < nMinX) { // 최소값 보다 작을 때
5450					do {
5451						nX += (nMaxX - nMinX);
5452					} while (nX < nMinX); 
5453				} else if (nX > nMaxX) { // 최대값 보다 클 때
5454					do {
5455						nX -= (nMaxX - nMinX);
5456					} while (nX > nMaxX);
5457				}
5458			} else {
5459				nX = Math.max(nMinX, nX);
5460				nX = Math.min(nMaxX, nX);
5461			}
5462			
5463			if (nX !== this._htOption.x) {
5464				// 절대를 상대 값으로
5465				this.set("x", nX, true);
5466			}
5467		}
5468		
5469		if (this._htOption.rangeY) {
5470			nMinY = this._htOption.rangeY[0];
5471			nMaxY = this._htOption.rangeY[1];
5472			
5473			if (this._htOption.positionRepeat) {
5474				if (nY < nMinY) { // 최소값 보다 작을 때
5475					do {
5476						nY += (nMaxY - nMinY);
5477					} while (nY < nMinY); 
5478				} else if (nY >
5478 nMaxY) { // 최대값 보다 클 때
5479					do {
5480						nY -= (nMaxY - nMinY);
5481					} while (nY > nMaxY);
5482				}
5483			} else {
5484				nY = Math.max(nMinY, nY);
5485				nY = Math.min(nMaxY, nY);
5486			}
5487			
5488			if (nY !== this._htOption.y) {
5489				this.set("y", nY, true);
5490			}
5491		}
5492	},
5493	
5494	/**
5495	 * hitArea 옵션이 배열로 들어올 경우 boundary를 구해서 저장해놓는다
5496	 * @private
5497	 */
5498	_makeHitAreaBoundary : function () {
5499		this._htHitAreaBoundary = collie.util.getBoundary(this._htOption.hitArea);
5500	},
5501	
5502	/**
5503	 * 객체의 위치를 정렬한다.
5504	 * 
5505	 * @param {String|Boolean} [sHorizontal=center] 수평 정렬 [left|right|center], false면 정렬하지 않음
5506	 * @param {String|Boolean} [sVertical=center] 수직 정렬 [top|bottom|center], false면 정렬하지 않음
5507	 * @param {collie.DisplayObject} [oBaseObject] 기준 객체, 값이 없을 경우 부모, 부모가 없을 경우 레이어를  기준으로 정렬 한다.
5508	 */
5509	align : function (sHorizontal, sVertical, oBaseObject) {
5510		if (!this.getLayer()) {
5511			return;
5512		}
5513		
5514		oBaseObject = oBaseObject || this.getParent();
5515		var nWidth = 0;
5516		var nHeight = 0;
5517		var nX = 0;
5518		var nY = 0;
5519		
5520		// 기준 크기 구함
5521		if (oBaseObject) {
5522			nWidth = oBaseObject._htOption.width;
5523			nHeight = oBaseObject._htOption.height;
5524		} else {
5525			nWidth = this._oLayer._htOption.width;
5526			nHeight = this._oLayer._htOption.height;
5527		}
5528		
5529		if (sHorizontal !== false) {
5530			nX = (sHorizontal === "right") ? nWidth - this._htOption.width : nWidth / 2 - this._htOption.width / 2;
5531			this.set("x", nX);
5532		}
5533
5534		if (sVertical !== false) {
5535			nY = (sVertical === "bottom") ? nHeight - this._htOption.height : nHeight / 2 - this._htOption.height / 2;
5536			this.set("y", nY);
5537		}
5538	},
5539	
5540	/**
5541	 * 객체의 위치를 우측 기준으로 좌표만큼 이동한다
5542	 * 만일 Layer에 붙은 상태가 아니라면 붙은 후에 이동할 수 있도록 해 준다
5543	 * 
5544	 * @param {Number} nPosition 우측 기준 x좌표
5545	 * @return {collie.DisplayObject} 자기 자신을 반환
5546	 */
5547	right : function (nPosition) {
5548		var nWidth = 0;
5549		
5550		// 기준 크기 구함
5551		if (this._oParent) {
5552			nWidth = this._oParent._htOption.width;
5553		}
5554		
5555		if (!nWidth && this._oLayer) {
5556			nWidth = this._oLayer._htOption.width;
5557		}
5558		
5559		// 크기가 구해졌을 때만 정렬
5560		if (nWidth) {
5561			this.set("x", nWidth - (this._htOption.width + nPosition));
5562		} else {
5563			this._nPositionRight = nPosition;
5564		}
5565		
5566		return this;
5567	},
5568	
5569	/**
5570	 * 객체의 위치를 하단 기준으로 좌표만큼 이동한다
5571	 * 만일 Layer에 붙은 상태가 아니라면 붙은 후에 이동할 수 있도록 해 준다
5572	 * 
5573	 * @param {Number} nPosition 하단 기준 x좌표
5574	 * @return {collie.DisplayObject} 자기 자신을 반환
5575	 */
5576	bottom : function (nPosition) {
5577		var nHeight = 0;
5578		
5579		// 기준 크기 구함
5580		if (this._oParent) {
5581			nHeight = this._oParent.get("height");
5582		}
5583		
5584		if (!nHeight && this._oLayer) {
5585			nHeight = this._oLayer.option("height");
5586		}
5587		
5588		// 크기가 구해졌을 때만 정렬
5589		if (nHeight) {
5590			this.set("y", nHeight - (this._htOption.height + nPosition));
5591		} else {
5592			this._nPositionBottom = nPosition;
5593		}
5594		
5595		return this;
5596	},
5597	
5598	/**
5599	 * 지정한 비율에 맞게 크기를 변경 한다. 리샘플링과는 다르다
5600	 * 인자 둘 중 하나를 설정하면 설정한 부분의 비율에 맞춰서 크기를 변경 한다
5601	 * 
5602	 * @param {Number} [nWidth] 너비
5603	 * @param {Number} [nHeight] 높이
5604	 */
5605	resizeFixedRatio : function (nWidth, nHeight) {
5606		if (this.getImage()) {
5607			var nImageWidth = this.getImage().width;
5608			var nImageHeight = this.getImage().height;
5609			
5610			if (nWidth) {
5611				nHeight = nWidth * (nImageHeight / nImageWidth);
5612			} else if (nHeight) {
5613				nWidth = nHeight * (nImageWidth / nImageHeight);
5614			}
5615			
5616			this.set("width", Math.round(nWidth));
5617			this.set("height", Math.round(nHeight));
5618		}
5619	},
5620	
5621	/**
5622	 * Sprite 위치를 설정
5623	 * offsetX, offsetY로 값을 설정할 경우에 spriteX, spriteY는 정상적으로 동기화되지 못하는 문제가 있음 역추적 불가능
5624	 * @private
5625	 * @param {String} sKey 속성 이름
5626	 * @param {Number} nValue 값
5627	 */
5628	_setSpritePosition : function (sKey, nValue) {
5629		if (this._elImage && nValue !== null) {
5630			// spriteSheet 사용 시
5631			if (this._htOption.spriteSheet !== null) {
5632				var sheet = this._htSpriteSheet[this._htOption.spriteSheet];
5633				var nOffsetX; 
5634				var nOffsetY;
5635				
5636				if (sKey === "spriteSheet" && this._htSpriteSheet && this._htSpriteSheet[nValue]) {
5637					if (typeof sheet[0][0] !== "undefined") {
5638						if (this._htOption.spriteX !== null) { // 이미 spriteX가 있다면
5639							nOffsetX = sheet[this._htOption.spriteX][0];
5640							nOffsetY = sheet[this._htOption.spriteX][1];
5641						} else {
5642							nOffsetX = sheet[0][0];
5643							nOffsetY = sheet[0][1];
5644						}
5645					} else {
5646						nOffsetX = sheet[0];
5647						nOffsetY = sheet[1];
5648					}
5649					
5650					// 초기 위치 잡아줌
5651					this.set("offsetX", nOffsetX, true);
5652					this.set("offsetY", nOffsetY, true);
5653				} else if (sKey === "spriteX" && typeof sheet[nValue] !== "undefined") {
5654					this.set("offsetX", sheet[nValue][0], true);
5655					this.set("offsetY", sheet[nValue][1], true);
5656				}
5657			} else {
5658				var htImageSize = this.getImageSize();
5659				var nWidth = this._htOption.width;
5660				var nHeight = this._htOption.height;
5661				var nSpriteLength = this._htOption.spriteLength - 1; // 0부터 시작
5662				var nMaxSpriteX = (htImageSize.width / this._htOption.width) - 1;
5663				var nMaxSpriteY = (htImageSize.height / this._htOption.height) - 1;
5664				var nMaxOffsetX = htImageSize.width - 1;
5665				var nMaxOffsetY = htImageSize.height - 1;
5666				
5667				// spriteLength가 적용되어 있는 경우 최대 offset이 변경 됨
5668				if (nSpriteLength >= 0 && nHeight < htImageSize.height) {
5669					nMaxOffsetX = nMaxSpriteX * htImageSize.width;
5670					nMaxOffsetY = nMaxSpriteY * htImageSize.height;
5671				}
5672				
5673				switch (sKey) {
5674					case "spriteX" :
5675						var nOffsetX = 0;
5676						var nOffsetY = 0;
5677						
5678						// sprite길이를 지정했고 그게 최대 스프라이트 수보다 크다면 그것을 따라감
5679						if (nSpriteLength > nMaxSpriteX && nHeight < htImageSize.height) {
5680							nOffsetY = Math.floor(nValue / (nMaxSpriteX + 1)) * nHeight;
5681							nOffsetX = (nValue % (nMaxSpriteX + 1)) * nWidth;
5682						} else {
5683							nOffsetX = Math.min(nValue, nMaxSpriteX) * nWidth;
5684						}
5685						
5686						//TODO android 성능 문제, DisplayObject#set, timer, Animation#triggerCallback, spriteX 처리
5687						this.set("offsetX", nOffsetX, true);
5688						this.set("offsetY", nOffsetY, true);
5689						break;
5690						
5691					case "spriteY" :
5692						nValue = Math.min(nValue, nMaxSpriteY);
5693						this.set("offsetY", nValue * nHeight, true);
5694						break;
5695				}
5696			}
5697		}
5698	},
5699	
5700	/**
5701	 * attach된 이벤트 핸들러가 있는지 여부를 반환
5702	 *
5703	 * @return {Boolean}
5704	 */
5705	hasAttachedHandler : function () {
5706		if (
5707			this._htHandler && 
5708			(("click" in this._htHandler) && this._htHandler.click.length > 0) ||  
5709			(("mousedown" in this._htHandler) && this._htHandler.mousedown.length > 0) ||  
5710			(("mouseup" in this._htHandler) && this._htHandler.mouseup.length > 0)  
5711			) {
5712			return true;
5713		} else {
5714			return false;
5715		}
5716	},
5717	
5718	/**
5719	 * 특정 속도로 해당 지점까지 이동
5720	 * 
5721	 * @param {Number} nX 가고자 하는 곳의 x 좌표
5722	 * @param {Number} nY 가고자 하는 곳의 y 좌표
5723	 * @param {Number} nVelocity 초당 이동 거리(px), 속도가 0 이면 바로 이동한다.
5724	 * @param {Function} fCallback 이동이 끝난 후 실행될 콜백
5725	 * @param {collie.DisplayObject} fCallback.displayobject 현재 객체가 인자로 넘어감=
5726	 * @return {collie.AnimationTransition} 이동에 사용되는 타이머를 반환
5727	 */
5728	move : function (nX, nY, nVelocity, fCallback) {
5729		var nCurrentX = this._htOption.x;
5730		var nCurrentY = this._htOption.y;
5731		var nDistance = collie.util.getDistance(nCurrentX, nCurrentY, nX, nY);
5732		var nDuration = Math.round((nDistance / nVelocity) * 1000);
5733		
5734		if (this._oTimerMove !== null) {
5735			this._oTimerMove.stop();
5736			this._oTimerMove = null;
5737		}
5738		
5739		// duration이 없을 정도로 짧거나 속도가 0일 경우 Timer를 이용하지 않고 바로 이동
5740		if (!nVelocity || nDuration < collie.Renderer.getInfo().fps) {
5741			this.set({
5742				x : nX,
5743				y : nY
5744			});
5745			
5746			
5747			if (fCallback) {
5748				fCallback(this);
5749			}
5750		} else {
5751			var htOption = {
5752				from : [nCurrentX, nCurrentY],
5753				to : [nX, nY],
5754				set : ["x", "y"]
5755			};
5756			
5757			if (fCallback) {
5758				htOption.onComplete = function () {
5759					fCallback(this);
5760				};
5761			}
5762			
5763			this._oTimerMove = collie.Timer.transition(this, nDuration, htOption);
5764			return this._oTimerMove;
5765		}
5766	},
5767	
5768	/**
5769	 * 상대 경로로 이동
5770	 * 
5771	 * @param {Number} nX 가고자 하는 곳의 x 좌표
5772	 * @param {Number} nY 가고자 하는 곳의 y 좌표
5773	 * @param {Number} nVelocity 초당 이동 거리(px), 속도가 0 이면 바로 이동한다.
5774	 * @param {Function} fCallback 이동이 끝난 후 실행될 콜백
5775	 * @return {collie.AnimationTransition} 이동에 사용되는 타이머를 반환
5776	 */
5777	moveBy : function (nX, nY, nVelocity, fCallback) {
5778		var nCurrentX = this._htOption.x;
5779		var nCurrentY = this._htOption.y;
5780		return this.move(nCurrentX + nX, nCurrentY + nY, nVelocity, fCallback);
5781	},
5782	
5783	/**
5784	 * 문자열로 클래스 정보 반환
5785	 * 
5786	 * @return {String}
5787	 */
5788	toString : function () {
5789		return "DisplayObject" + (this._htOption.name ? " " + this._htOption.name : "")+ " #" + this.getId() + (this.getImage() ? "(image:" + this.getImage().src + ")" : "");
5790	},
5791	
5792	/**
5793	 * 객체 복사
5794	 * 이벤트는 복사되지 않는다.
5795	 * @param {Boolean} bRecursive 자식까지 모두 복사하는지 여부
5796	 * @return {collie.DisplayObject}
5797	 * @example
5798	 * var box = new collie.DisplayObject({
5799	 * 	width: 100,
5800	 * 	height: 100,
5801	 * 	backgroundColor: "blue"
5802	 * }).addTo(layer);
5803	 * 
5804	 * var box2 = box.clone().addTo(layer);
5805	 */
5806	clone : function (bRecursive) {
5807		var oDisplayObject = new this.constructor(this._htOption);
5808		
5809		if (bRecursive && this._aDisplayObjects.length) {
5810			for (var i = 0, l = this._aDisplayObjects.length; i < l; i++) {
5811				this._aDisplayObjects[i].clone(true).addTo(oDisplayObject);
5812			}
5813		}
5814		
5815		return oDisplayObject;
5816	}
5817}, collie.Component);
5818
5819/**
5820 * 표시 객체 아이디를 할당한다. 1씩 늘어남
5821 * 
5822 * @static
5823 * @private
5824 */
5825collie.DisplayObject._idx = 0;
5826
5827/**
5828 * 생성된 표시 객체를 담는다. Layer에 추가하지 않아도 표시 객체를 아이디로만 가져올 수 있다
5829 * 
5830 * @static
5831 * @private
5832 */
5833collie.DisplayObject.htFactory = {};
5834/**
5835 * 속도, 가속도, 마찰력, 질량을 포함한 표시 객체
5836 * - rotate는 마찰력이 없다
5837 * @class collie.MovableObject
5838 * @deprecated DisplayObject에 기능 추가
5839 * @extends collie.DisplayObject
5840 * @param {Object} [htOption] 설정
5841 * @param {Number} [htOption.velocityX=0] x축 속도(초당 px)
5842 * @param {Number} [htOption.velocityY=0] y축 속도(초당 px)
5843 * @param {Number} [htOption.velocityRotate=0] 회전 속도(초당 1도)
5844 * @param {Number} [htOption.forceX=0] x축 힘(초당 px)
5845 * @param {Number} [htOption.forceY=0] y축 힘(초당 px)
5846 * @param {Number} [htOption.forceRotate=0] 회전 힘(초당 1도)
5847 * @param {Number} [htOption.mass=1] 질량
5848 * @param {Number} [htOption.friction=0] 마찰력
5849 * @param {Boolean} [htOption.useRealTime=true] SkippedFrame을 적용해서 싸이클을 현재 시간과 일치
5850 */
5851collie.MovableObject = collie.Class(/** @lends collie.MovableObject.prototype */{
5852	/**
5853	 * @constructs
5854	 */
5855	$init : function (htOption) {
5856		// this.option({
5857			// velocityX : 0,
5858			// velocityY : 0,
5859			// velocityRotate : 0,
5860			// forceX : 0,
5861			// forceY : 0,
5862			// forceRotate : 0,
5863			// mass : 1, // 질량
5864			// friction : 0, // 마찰
5865			// useRealTime : true
5866		// }, null, true);
5867	}
5868	
5869	/**
5870	 * 화면을 업데이트
5871	 * 
5872	 * @private
5873	 */
5874	// update : function (nFrameDuration, nX, nY, nLayerWidth, nLayerHeight) {
5875		// var nFrame = Math.max(17, nFrameDuration) / 1000;
5876// 		
5877		// // skippedFrame 적용을 하지 않는다면 1frame 씩만 그림
5878		// if (!this._htOption.useRealTime) {
5879			// nFrame = 1;
5880		// }
5881// 		
5882		// this._applyForce(nFrame);
5883		// this._applyRotation(nFrame);
5884		// this.constructor.$super.update.call(this, nFrameDuration, nX, nY, nLayerWidth, nLayerHeight);
5885// 		
5886		// // 움직임이 있으면 다시 바뀐 상태로 둠
5887		// if (
5888				// this._htOption.velocityX !== 0 ||
5889				// this._htOption.velocityY !== 0 ||
5890				// this._htOption.velocityRotate !== 0 ||
5891				// this._htOption.forceX !== 0 ||
5892				// this._htOption.forceY !== 0 ||
5893				// this._htOption.forceRotate !== 0
5894				// ) {
5895			// this.setChanged(true);
5896		// }
5897	// },
5898	
5899	/**
5900	 * @private
5901	 */
5902	// _getValueDirection : function (nValue) {
5903		// return Math.abs(nValue) / nValue;
5904	// },
5905	
5906	/**
5907	 * 회전 힘, 속도를 반영
5908	 * @private
5909	 */
5910	// _applyRotation : function (nFrame) {
5911		// if (this._htOption.forceRotate !== 0) {
5912			// this.set("velocityRotate", this._htOption.velocityRotate + this._htOption.forceRotate);
5913		// }
5914// 		
5915		// if (this._htOption.velocityRotate !== 0) {
5916			// var nAngleRad = collie.util.fixAngle(collie.util.toRad(this._htOption.angle + this._htOption.velocityRotate * nFrame));
5917			// this.set("angle", Math.round(collie.util.toDeg(nAngleRad) * 1000) / 1000);
5918		// }
5919	// },
5920	
5921	/**
5922	 * 힘을 속도에 반영
5923	 *  
5924	 * @private
5925	 */
5926	// _applyForce : function (nFrame) {
5927		// var htInfo = this.get();
5928		// var nVelocityX = htInfo.velocityX;
5929		// var nVelocityY = htInfo.velocityY;
5930		// var nX = htInfo.x;
5931		// var nY = htInfo.y;
5932// 		
5933		// // 힘 적용 a = F / m
5934		// nVelocityX += (htInfo.forceX / htInfo.mass) * nFrame;
5935		// nVelocityY += (htInfo.forceY / htInfo.mass) * nFrame;
5936// 		
5937		// // 마찰력 적용
5938		// var nForceFrictionX = htInfo.friction * nVelocityX * htInfo.mass * nFrame;
5939		// var nForceFrictionY = htInfo.friction * nVelocityY * htInfo.mass * nFrame;
5940// 		
5941		// if (nVelocityX !== 0) {
5942			// nVelocityX = (this._getValueDirection(nVelocityX) !== this._getValueDirection(nVelocityX - nForceFrictionX)) ? 0 : nVelocityX - nForceFrictionX;
5943		// }
5944// 		
5945		// if (nVelocityY !== 0) {
5946			// nVelocityY = (this._getValueDirection(nVelocityY) !== this._getValueDirection(nVelocityY - nForceFrictionY)) ? 0 : nVelocityY - nForceFrictionY;
5947		// }
5948// 		
5949		// nX += nVelocityX * nFrame;
5950		// nY += nVelocityY * nFrame;
5951		// nVelocityX = Math.floor(nVelocityX * 1000) / 1000;
5952		// nVelocityY = Math.floor(nVelocityY * 1000) / 1000;
5953// 		
5954		// if (htInfo.friction && Math.abs(nVelocityX) < 0.05) {
5955			// nVelocityX = 0;
5956		// }
5957// 		
5958		// if (htInfo.friction && Math.abs(nVelocityY) < 0.05) {
5959			// nVelocityY = 0;
5960		// }
5961// 	
5962		// // 변경이 있을 때만 설정
5963		// if (
5964			// nX != htInfo.x ||
5965			// nY != htInfo.y ||
5966			// nVelocityX != htInfo.velocityX ||
5967			// nVelocityY != htInfo.velocityY
5968		// ) {
5969			// this.set({
5970				// x : nX,
5971				// y : nY,
5972				// velocityX : nVelocityX,
5973				// velocityY : nVelocityY
5974			// });
5975		// }
5976	// },
5977	
5978	/**
5979	 * 문자열로 클래스 정보 반환
5980	 * 
5981	 * @deprecated
5982	 * @return {String}
5983	 */
5984	// toString : function () {
5985		// return "MovableObject" + (this._htOption.name ? " " + this._htOption.name : "")+ " #" + this.getId() + (this.getImage() ? "(image:" + this.getImage().src + ")" : "");
5986	// }
5987}, collie.DisplayObject);
5988/**
5989 * Rectangle
5990 * - Rounded
5991 * @class collie.Rectangle
5992 * @extends collie.DisplayObject
5993 * @param {Object} [htOption] 설정
5994 * @param {Number} [htOption.radius=0] 테두리 굴림 값 (px)
5995 * @param {String} [htOption.strokeColor] 테두리 색상
5996 * @param {Number} [htOption.strokeWidth=0] 테두리 굵기(0이면 테두리 없음)
5997 * @param {String} [htOption.fillColor] 채울 색상(없으면 투명)
5998 * @param {String} [htOption.fillImage] 채울 이미지
5999 */
6000collie.Rectangle = collie.Class(/** @lends collie.Rectangle.prototype */{
6001	/**
6002	 * @constructs
6003	 */
6004	$init : function (htOption) {
6005		this.option({
6006			radius : 0,
6007			strokeColor : '',
6008			strokeWidth : 0,
6009			fillColor : '',
6010			fillImage : ''
6011		}, null, true);
6012		
6013		this._sBorderRadius = collie.util.getCSSPrefix("border-radius", true);
6014	},
6015	
6016	/**
6017	 * Delegate
6018	 * @private
6019	 */
6020	onDOMDraw : function (oEvent) {
6021		if (this._bChanged) {
6022			if (this._htOption.radius) {
6023				oEvent.element.style[this._sBorderRadius] = this._htOption.radius + "px";
6024				oEvent.element.style.borderRadius = this._htOption.radius + "px";
6025			}
6026			
6027			if (this._htOption.fillImage) {
6028				collie.ImageManager.getImage(this._htOption.fillImage, function (el) {
6029					oEvent.element.style.backgroundImage = "url('" + el.src + "')";
6030				});
6031			} else if (this._htOption.fillColor) {
6032				oEvent.element.style.backgroundColor = this._htOption.fillColor;
6033			}
6034			
6035			if (this._htOption.strokeWidth) {
6036				oEvent.element.style.border = this._htOption.strokeWidth + "px solid " + this._htOption.strokeColor;
6037			}
6038			
6039			this._bChanged = false;
6040		}		
6041	},
6042	
6043	/**
6044	 * Delegate
6045	 * @private
6046	 */
6047	onCanvasDraw : function (oEvent) {
6048		var oContext = oEvent.context;
6049		var nRadius = this._htOption.radius;
6050		var bIsRetinaDisplay = collie.Renderer.isRetinaDisplay();
6051		var nWidth = this._htOption.width;
6052		var nHeight = this._htOption.height;
6053		var nStrokeWidth = this._htOption.strokeWidth;
6054		
6055		// 레티나 디스플레이 대응
6056		if (bIsRetinaDisplay) {
6057			nWidth *= 2;
6058			nHeight *= 2;
6059			nRadius *= 2;
6060			nStrokeWidth *= 2;
6061		}
6062		
6063		if (htInfo.fillImage) {
6064			var el = collie.ImageManager.getImage(htInfo.fillImage);
6065			
6066			if (!el) {
6067				collie.ImageManager.getImage(htInfo.fillImage, function () {
6068					this.setChanged();
6069				}.bind(this));
6070			} else {
6071				var pattern = oContext.createPattern(el, "repeat");
6072				oContext.fillStyle = pattern;
6073			}
6074		} else if (htInfo.fillColor) {
6075			oContext.fillStyle = htInfo.fillColor;
6076		}
6077		
6078		if (this._htOption.strokeColor) {
6079			oContext.strokeStyle = this._htOption.strokeColor;
6080		}
6081		
6082		if (this._htOption.strokeWidth) {
6083			oContext.lineWidth = nStrokeWidth;
6084		}
6085		
6086		if (nRadius) {
6087			oContext.save();
6088			oContext.translate(oEvent.x, oEvent.y);
6089			oContext.beginPath();
6090			oContext.moveTo(nRadius, 0);
6091			oContext.lineTo(nWidth - nRadius, 0);
6092			oContext.quadraticCurveTo(nWidth, 0, nWidth, nRadius);
6093			oContext.lineTo(nWidth, nHeight - nRadius);
6094			oContext.quadraticCurveTo(nWidth, nHeight, nWidth - nRadius, nHeight);
6095			oContext.lineTo(nRadius, nHeight);
6096			oContext.quadraticCurveTo(0, nHeight, 0, nHeight - nRadius);
6097			oContext.lineTo(0, nRadius);
6098			oContext.quadraticCurveTo(0, 0, nRadius, 0);
6099			oContext.closePath();
6100			oContext.restore();
6101			
6102			if (this._htOption.fillColor || this._htOption.fillImage) {
6103				oContext.fill();
6104			}    
6105			
6106			if (this._htOption.strokeWidth) {
6107				oContext.stroke();
6108			}
6109		} else {
6110			if (this._htOption.fillColor || this._htOption.fillImage) {
6111				oContext.fillRect(oEvent.x, oEvent.y, nWidth, nHeight);
6112			}
6113			
6114			if (this._htOption.strokeWidth) {
6115				oContext.strokeRect(oEvent.x, oEvent.y, nWidth, nHeight);
6116			}
6117		}
6118		
6119		this._bChanged = false;
6120	},
6121	
6122	/**
6123	 * 문자열로 클래스 정보 반환
6124	 * 
6125	 * @return {String}
6126	 */
6127	toString : function () {
6128		return "Rectangle" + (this.get("name") ? " " + this.get("name") : "")+ " #" + this.getId() + (this.getImage() ? "(image:" + this.getImage().src + ")" : "");
6129	}
6130}, collie.DisplayObject);
6131/**
6132 * Drawing Circle
6133 * If you want to see a circle using DOM Rendering, you need the <a href="http://raphaeljs.com" target="_blank">Raphael.js</a> 2.1.0 or above.
6134 * 
6135 * @class
6136 * @extends collie.DisplayObject
6137 * @requires http://raphaeljs.com
6138 * @param {Object} [htOption] Options
6139 * @param {Number} [htOption.radius=0] Radius(px)
6140 * @param {String} [htOption.strokeColor] Stroke color
6141 * @param {Number} [htOption.strokeWidth=0] Border width. It'll be disappear when you set this option as 0.
6142 * @param {String} [htOption.fillColor] Inside color. The Default value is a transparent color.
6143 * @param {String} [htOption.fillImage] Fill the image inside a polyline
6144 * @param {Number} [htOption.startAngle=0] Starting Angle(degree)
6145 * @param {Number} [htOption.endAngle=360] Ending Angle(degree), The Circle would be fully filled when you set starting angle as 0 and set ending angle as 360.
6146 * @param {Boolean} [htOption.closePath=false] Closing a Path. like a pac-man.
6147 * @param {Boolean} [htOption.autoExpand=true] When this options set as true, the circle object expands to fit size to diameter.
6148 * @param {Boolean} [htOption.anticlockwise=false] The Circle will be filled anticlockwise when you set this option as true.
6149 * @example
6150 * // Draw a Circle
6151 * var circle = new collie.Circle({
6152 * 	radius : 20 // The circle object just expands to fit size to diameter. (width:40, height:40)
6153 * }).addTo(layer);
6154 * 
6155 * // arc
6156 * circle.set({
6157 * 	startAngle : 0,
6158 * 	endAngle : 270
6159 * });
6160 * 
6161 * // a pac-man
6162 * circle.set({
6163 * 	startAngle : 45,
6164 * 	endAngle : 315,
6165 * 	closePath : true
6166 * });
6167 */
6168collie.Circle = collie.Class(/** @lends collie.Circle.prototype */{
6169	/**
6170	 * @constructs
6171	 */
6172	$init : function (htOption) {
6173		this.option({
6174			radius : 0,
6175			strokeColor : '#000000',
6176			strokeWidth : 0,
6177			fillColor : '',
6178			fillImage : '',
6179			startAngle : 0,
6180			endAngle : 360,
6181			closePath : false,
6182			anticlockwise : false,
6183			autoExpand : true
6184		}, null, true);
6185		
6186		this._oPaper = null;
6187		this.optionSetter("radius", this._expandSize.bind(this));
6188		this._expandSize();
6189	},
6190	
6191	_expandSize : function () {
6192		if (this._htOption.autoExpand && this._htOption.radius) {
6193			var size = this._htOption.radius * 2 + this._htOption.strokeWidth;
6194			this.set("width", size);
6195			this.set("height", size);
6196		}
6197	},
6198	
6199	/**
6200	 * Delegate
6201	 * @private
6202	 */
6203	onDOMDraw : function (oEvent) {
6204		var el = oEvent.element;
6205		
6206		if (typeof Raphael === "undefined") {
6207			return;
6208		}
6209		
6210		var htInfo = this.get();
6211		var nStrokeWidth = htInfo.strokeWidth;
6212		var nRadius = htInfo.radius;
6213		var nWidth = htInfo.width;
6214		var nHeight = htInfo.height;
6215		var htDirty = this.getDirty();
6216		var circle;
6217		
6218		if (this._oPaper === null) {
6219			this._oPaper = Raphael(el, nWidth + nStrokeWidth, nHeight + nStrokeWidth);
6220			this._oPaper.canvas.style.zIndex = 10;
6221		} else if (htDirty && (htDirty.width || htDirty.height)) {
6222			this._oPaper.setSize(nWidth, nHeight);
6223		}
6224		
6225		el.style.left = -(nStrokeWidth / 2) + "px";
6226		el.style.top = -(nStrokeWidth / 2) + "px";
6227		this._oPaper.clear();
6228		
6229		if (nRadius) {
6230			var rx = nRadius;
6231			var ry = nRadius;
6232			var x1 = rx + nRadius * Math.cos(collie.util.toRad(htInfo.startAngle));
6233			var y1 = ry + nRadius * Math.sin(collie.util.toRad(htInfo.startAngle));
6234			var x2 = rx + nRadius * Math.cos(collie.util.toRad(htInfo.endAngle));
6235			var y2 = ry + nRadius * Math.sin(collie.util.toRad(htInfo.endAngle));
6236			var angle = htInfo.anticlockwise ? htInfo.startAngle - htInfo.endAngle : htInfo.endAngle - htInfo.startAngle;
6237			
6238			if (Math.abs(angle) >= 360) {
6239			  angle = 360;
6240			} else if (angle < 0) {
6241			  angle += 360;
6242			}
6243			
6244			var flag1 = (angle > 180 ? 1 : 0);
6245			var flag2 = htInfo.anticlockwise ? 0 : 1;
6246			
6247			if (angle >= 360) {
6248				circle = this._oPaper.circle(rx, ry, nRadius);
6249			} else {
6250				circle = this._oPaper.path("M" + x1 + "," + y1 + "a" + nRadius + "," + nRadius + ",0," + flag1 + "," + flag2 + "," + (x2 -x1) + "," + (y2 -y1) + (htInfo.closePath ? "L" + rx + "," + ry + "L" + x1 + "," + y1 + "Z" : ""));
6251			}
6252		}
6253		
6254		if (circle) {
6255			circle.transform("t" + (nStrokeWidth / 2) + "," + (nStrokeWidth / 2));
6256			
6257			if (htInfo.fillImage) {
6258				collie.ImageManager.getImage(htInfo.fillImage, function (el) {
6259					circle.attr("fill", "url('" + el.src + "')");
6260				});
6261			} else if (htInfo.fillColor) {
6262				circle.attr("fill", htInfo.fillColor);
6263			}
6264			
6265			if (htInfo.strokeColor) {
6266				circle.attr("stroke", htInfo.strokeColor);
6267			}
6268			
6269			circle.attr("stroke-width", htInfo.strokeWidth);
6270		}
6271	},
6272	
6273	/**
6274	 * Delegate
6275	 * @private
6276	 */
6277	onCanvasDraw : function (oEvent) {
6278		var htInfo = this.get();		
6279		var oContext = oEvent.context;
6280		var nX = oEvent.x;
6281		var nY = oEvent.y;
6282		var bIsRetinaDispaly = collie.Renderer.isRetinaDisplay();
6283		var nRadius = htInfo.radius;
6284		var nStrokeWidth = htInfo.strokeWidth;
6285		var nWidth = htInfo.width;
6286		var nHeight = htInfo.height;
6287		
6288		if (bIsRetinaDispaly) {
6289			nWidth *= 2;
6290			nHeight *= 2;
6291			nRadius *= 2;
6292			nStrokeWidth *= 2;
6293		}
6294		
6295		if (htInfo.fillImage) {
6296			var el = collie.ImageManager.getImage(htInfo.fillImage);
6297			
6298			if (!el) {
6299				collie.ImageManager.getImage(htInfo.fillImage, function () {
6300					this.setChanged();
6301				}.bind(this));
6302			} else {
6303				var pattern = oContext.createPattern(el, "repeat");
6304				oContext.fillStyle = pattern;
6305			}
6306		} else if (htInfo.fillColor) {
6307			oContext.fillStyle = htInfo.fillColor;
6308		}
6309		
6310		if (htInfo.strokeColor) {
6311			oContext.strokeStyle = htInfo.strokeColor;
6312		}
6313		
6314		if (nStrokeWidth) {
6315			oContext.lineWidth = nStrokeWidth;
6316		}
6317		
6318		if (nRadius) {
6319			var rx = nX + nRadius;
6320			var ry = nY + nRadius;
6321			var bFullCircle = Math.abs(htInfo.startAngle - htInfo.endAngle) >= 360;
6322			
6323			oContext.beginPath();
6324			
6325			if (htInfo.closePath && !bFullCircle) {
6326				oContext.moveTo(rx, ry);
6327			}
6328			
6329			oContext.arc(rx, ry, nRadius, collie.util.toRad(htInfo.startAngle), collie.util.toRad(htInfo.endAngle), htInfo.anticlockwise);			
6330			
6331			if (htInfo.closePath) {
6332				oContext.closePath();
6333			}
6334			
6335			if (htInfo.fillColor || htInfo.fillImage) {
6336				oContext.fill();
6337			}    
6338			
6339			if (htInfo.strokeWidth) {
6340				oContext.stroke();
6341			}
6342			
6343		}
6344	},
6345	
6346	/**
6347	 * Move a position by a center of the circle.
6348	 * 
6349	 * @param {Number} nCenterX
6350	 * @param {Number} nCenterY
6351	 * @return {collie.Circle} For the method chaining
6352	 */
6353	center : function (nCenterX, nCenterY) {
6354		this.set("x", nCenterX - this._htOption.radius);
6355		this.set("y", nCenterY - this._htOption.radius);
6356		return this;
6357	},
6358	
6359	/**
6360	 * Returns information of The Class as String
6361	 * 
6362	 * @return {String}
6363	 */
6364	toString : function () {
6365		return "Circle" + (this._htOption.name ? " " + this._htOption.name : "")+ " #" + this.getId() + (this.getImage() ? "(image:" + this.getImage().src + ")" : "");
6366	}
6367}, collie.DisplayObject);
6368/**
6369 * Drawing Polyline
6370 * If you want to see a polyline using DOM Rendering, you need the <a href="http://raphaeljs.com" target="_blank">Raphael.js</a> 2.1.0 or above.
6371 * 
6372 * @class collie.Polyline
6373 * @extends collie.DisplayObject
6374 * @requires http://raphaeljs.com
6375 * @param {Object} [htOption] Options
6376 * @param {String} [htOption.strokeColor]
6377 * @param {Number} [htOption.strokeWidth=0] When this option set as 0, The stroke disappears.
6378 * @param {String} [htOption.fillColor]
6379 * @param {String} [htOption.fillImage] Fill the image inside a polyline
6380 * @param {String} [htOption.closePath=false] Closing a Path
6381 * @param {String} [htOption.dashArray] ["", "-", ".", "-.", "-..", ". ", "- ", "--", "- .", "--.", "--.."]
6382 * @param {String} [htOption.lineCap="butt"] ["butt", "square", "round"]
6383 * @param {String} [htOption.lineJoin="miter"] ["bevel", "round", "miter"]
6384 * @param {Number} [htOption.miterLimit=10]
6385 * @example
6386 * // Draw a Rectangle
6387 * var line = new collie.Polyline({
6388 * 	closePath : true
6389 * }).addTo(layer);
6390 * line.setPointData([
6391 * 	[0, 0],
6392 * 	[100, 0],
6393 * 	[100, 100],
6394 * 	[0, 100]
6395 * ]);
6396 * 
6397 * // using moveTo
6398 * line.moveTo(200, 0);
6399 * line.lineTo(300, 0);
6400 * line.lineTo(300, 100);
6401 * line.lineTo(200, 100);
6402 * line.lineTo(200, 0); // expand boundary and set change status.
6403 */
6404collie.Polyline = collie.Class(/** @lends collie.Polyline.prototype */{
6405	/**
6406	 * @constructs
6407	 */
6408	$init : function (htOption) {
6409		this.option({
6410			strokeColor : '#000000',
6411			strokeWidth : 1,
6412			fillColor : '',
6413			fillImage : '',
6414			lineCap : "butt",
6415			lineJoin : "miter",
6416			miterLimit : 10,
6417			dashArray : "",	
6418			closePath : false // 마지막을 자동으로 연결해 줌
6419		}, null, true);
6420		this._aPointData = [];
6421		this._oPaper = null;
6422		this._htPointBoundary = {
6423			right : null,
6424			bottom : null
6425		};
6426	},
6427	
6428	/**
6429	 * Set points for drawing
6430	 * 
6431	 * @param {Array} aPointData [[x1, y1, bMoveTo], [x2, y2, bMoveTo], ...]
6432	 * @param {Boolean} bSkipExpandSize You can this option set as true if you don't want to expand size
6433	 */
6434	setPointData : function (aPointData, bSkipExpandSize) {
6435		this._aPointData = aPointData;
6436		this.setChanged();
6437		
6438		if (!bSkipExpandSize) {
6439			this._expandBoundary(aPointData);
6440		}
6441	},
6442	
6443	/**
6444	 * Return points
6445	 * 
6446	 * @return {Array}
6447	 */
6448	getPointData : function () {
6449		return this._aPointData;
6450	},
6451	
6452	/**
6453	 * Add a point
6454	 * @param {Number} nX
6455	 * @param {Number} nY
6456	 * @param {Boolean} bSkipExpandSize You can this option set as true if you don't want to expand size
6457	 */	
6458	addPoint : function (nX, nY, bSkipExpandSize) {
6459		this._aPointData.push([nX, nY]);
6460		this.setChanged();
6461		
6462		if (!bSkipExpandSize) {
6463			this._expandBoundary(nX, nY);
6464		}
6465	},
6466	
6467	/**
6468	 * Move a cursor position
6469	 * 
6470	 * @param {Number} nX
6471	 * @param {Number} nY
6472	 */
6473	moveTo : function (nX, nY) {
6474		this._aPointData.push([nX, nY, true]);
6475	},
6476	
6477	/**
6478	 * Alias for the addPoint method
6479	 * @see collie.Polyline#addPoint
6480	 */
6481	lineTo : function (nX, nY, bSkipExpandSize) {
6482		this.addPoint(nX, nY, bSkipExpandSize);
6483	},
6484	
6485	/**
6486	 * Reset points
6487	 */
6488	resetPointData : function () {
6489		this._aPointData = [];
6490		this._htPointBoundary = {
6491			right : null,
6492			bottom : null
6493		};
6494		this.setChanged();
6495	},
6496	
6497	/**
6498	 * 포인트 영역을 늘린다
6499	 * @private
6500	 * 
6501	 * @param {Array|Number} nX 배열로 들어오면 배열을 돌면서 확장 한다
6502	 * @param {Number} nY
6503	 * @param {Boolean} bSkipAdoptSize 크기를 객체에 적용하는 것을 생략한다
6504	 */
6505	_expandBoundary : function (nX, nY, bSkipAdoptSize) {
6506		if (nX instanceof Array) {
6507			for (var i = 0, len = nX.length; i < len; i++) {
6508				this._expandBoundary(nX[i][0], nX[i][1], true);
6509			}
6510		} else {
6511			this._htPointBoundary.right = this._htPointBoundary.right === null ? nX : Math.max(nX, this._htPointBoundary.right);
6512			this._htPointBoundary.bottom = this._htPointBoundary.bottom === null ? nY : Math.max(nY, this._htPointBoundary.bottom);
6513		}
6514		
6515		// 크기 적용
6516		if (!bSkipAdoptSize) {
6517			var nStrokeWidth = this._htOption.strokeWidth * (collie.Renderer.isRetinaDisplay() ? 2 : 1);
6518			var nWidth = this._htPointBoundary.right + nStrokeWidth * 2;
6519			var nHeight = this._htPointBoundary.bottom + nStrokeWidth * 2; 
6520			this.set({
6521				width : nWidth,
6522				height : nHeight
6523			});
6524			
6525			
6526			if (this._oPaper !== null) {
6527				this._oPaper.setSize(nWidth, nHeight);
6528			}
6529		}
6530	},
6531	
6532	/**
6533	 * Delegate
6534	 * @private
6535	 */
6536	onDOMDraw : function (oEvent) {
6537		var el = oEvent.element;
6538		
6539		// 점이 2개 미만이면 그리지 않는다
6540		if (this._aPointData.length < 2) {
6541			return;
6542		}
6543		
6544		if (typeof Raphael === "undefined") {
6545			return;
6546		}
6547		
6548		var htInfo = this.get();
6549		var nStrokeWidth = htInfo.strokeWidth;
6550		var htDirty = this.getDirty();
6551		
6552		if (this._oPaper === null) {
6553			this._oPaper = Raphael(el, htInfo.width, htInfo.height);
6554			this._oPaper.canvas.style.zIndex = 10;
6555		} else if (htDirty && (htDirty.width || htDirty.height)) {
6556			this._oPaper.setSize(htInfo.width, htInfo.height);
6557		}
6558		
6559		el.style.left = -(nStrokeWidth / 2) + "px";
6560		el.style.top = -(nStrokeWidth / 2) + "px";
6561		this._oPaper.clear();
6562		
6563		var str = "M" + this._aPointData[0][0] + "," + this._aPointData[0][1];
6564		
6565		for (var i = 1, len = this._aPointData.length; i < len; i++) {
6566			str += (this._aPointData[i][2] ? "M" : "L") + this._aPointData[i][0] + "," + this._aPointData[i][1];
6567		}
6568		
6569		// 마지막이 연결되어 있지 않다면
6570		if (
6571			htInfo.closePath && (
6572				this._aPointData[0][0] !== this._aPointData[this._aPointData.length - 1][0] || 
6573				this._aPointData[0][1] !== this._aPointData[this._aPointData.length - 1][1]
6574			) 
6575		) {
6576			str += "L" + this._aPointData[0][0] + "," + this._aPointData[0][1];
6577		}
6578		
6579		var line = this._oPaper.path(str + (htInfo.closePath ? "Z" : ""));
6580		line.transform("t" + (nStrokeWidth / 2) + "," + (nStrokeWidth / 2));
6581		
6582		if (htInfo.fillImage) {
6583			collie.ImageManager.getImage(htInfo.fillImage, function (el) {
6584				line.attr("fill", "url('" + el.src + "')");
6585			});
6586		} else if (htInfo.fillColor) {
6587			line.attr("fill", htInfo.fillColor);
6588		}
6589		
6590		if (htInfo.lineCap) {
6591			line.attr("stroke-linecap", htInfo.lineCap);
6592		}
6593		
6594		if (htInfo.lineJoin) {
6595			line.attr("stroke-linejoin", htInfo.lineJoin);
6596		}
6597		
6598		if (htInfo.miterLimit !== null) {
6599			line.attr("stroke-miterlimit", htInfo.miterLimit);
6600		}
6601		
6602		if (htInfo.strokeColor) {
6603			line.attr("stroke", htInfo.strokeColor);
6604		}
6605		
6606		line.attr("stroke-width", nStrokeWidth);
6607		
6608		if (htInfo.dashArray) {
6609			line.attr("stroke-dasharray", htInfo.dashArray);
6610		}
6611	},
6612	
6613	/**
6614	 * Delegate
6615	 * @private
6616	 */
6617	onCanvasDraw : function (oEvent) {
6618		// 점이 2개 미만이면 그리지 않는다
6619		if (this._aPointData.length < 2) {
6620			return;
6621		}
6622		
6623		var htInfo = this.get();		
6624		var oContext = oEvent.context;
6625		var bIsRetinaDisplay = collie.Renderer.isRetinaDisplay();
6626		var nStrokeWidth = htInfo.strokeWidth;
6627		var nRatio = (bIsRetinaDisplay ? 2 : 1);
6628		
6629		oContext.save();
6630		oContext.translate(oEvent.x, oEvent.y);
6631		
6632		// 레티나 디스플레이 대응
6633		if (bIsRetinaDisplay) {
6634			nStrokeWidth *= 2;
6635		}
6636		
6637		if (htInfo.fillImage) {
6638			var el = collie.ImageManager.getImage(htInfo.fillImage);
6639			
6640			if (!el) {
6641				collie.ImageManager.getImage(htInfo.fillImage, function () {
6642					this.setChanged();
6643				}.bind(this));
6644			} else {
6645				var pattern = oContext.createPattern(el, "repeat");
6646				oContext.fillStyle = pattern;
6647			}
6648		} else if (htInfo.fillColor) {
6649			oContext.fillStyle = htInfo.fillColor;
6650		}
6651		
6652		if (htInfo.strokeColor) {
6653			oContext.strokeStyle = htInfo.strokeColor;
6654		}
6655		
6656		oContext.lineWidth = nStrokeWidth;
6657		
6658		if (htInfo.lineCap) {
6659			oContext.lineCap = htInfo.lineCap;
6660		}
6661		
6662		if (htInfo.lineJoin) {
6663			oContext.lineJoin = htInfo.lineJoin;
6664		}
6665		
6666		if (htInfo.miterLimit) {
6667			oContext.miterLimit = htInfo.miterLimit;
6668		}
6669		
6670		if (htInfo.dashArray) {
6671			// moveTo를 하면 fill이 안된다 연결된 선을 하나 더 그려서 해결한다
6672			if (htInfo.fillColor || htInfo.fillImage) {
6673				oContext.beginPath();
6674				oContext.moveTo(this._aPointData[0][0] * nRatio, this._aPointData[0][1] * nRatio);
6675				
6676				for (var i = 1, len = this._aPointData.length; i < len; i++) {
6677					if (this._aPointData[i][2]) {
6678						oContext.moveTo(this._aPointData[i][0] * nRatio, this._aPointData[i][1] * nRatio);
6679						continue;
6680					}
6681		
6682					oContext.lineTo(this._aPointData[i][0] * nRatio, this._aPointData[i][1] * nRatio);
6683				}
6684				
6685				if (htInfo.closePath) {
6686					oContext.closePath();
6687				}
6688				
6689				oContext.fill();
6690			}
6691			
6692			oContext.resetDashedLine();
6693		}		
6694		
6695		// 앞에 그린 선은 지워진다
6696		oContext.beginPath();
6697		oContext.moveTo(this._aPointData[0][0] * nRatio, this._aPointData[0][1] * nRatio);
6698		
6699		for (var i = 1, len = this._aPointData.length; i < len; i++) {
6700			// moveTo
6701			if (this._aPointData[i][2]) {
6702				oContext.moveTo(this._aPointData[i][0] * nRatio, this._aPointData[i][1] * nRatio);
6703				continue;
6704			}
6705
6706			if (htInfo.dashArray) {
6707				oContext.dashedLine(this._aPointData[i - 1][0] * nRatio, this._aPointData[i - 1][1] * nRatio, this._aPointData[i][0] * nRatio, this._aPointData[i][1] * nRatio, collie.raphaelDashArray[htInfo.dashArray], nStrokeWidth);
6708			} else {
6709				oContext.lineTo(this._aPointData[i][0] * nRatio, this._aPointData[i][1] * nRatio);
6710			}
6711		}
6712		
6713		// 마지막이 연결되어 있지 않다면
6714		if (
6715			htInfo.dashArray && htInfo.closePath && (
6716			this._aPointData[0][0] !== this._aPointData[this._aPointData.length - 1][0] || 
6717			this._aPointData[0][1] !== this._aPointData[this._aPointData.length - 1][1]
6718		)) {
6719			oContext.dashedLine(this._aPointData[i - 1][0] * nRatio, this._aPointData[i - 1][1] * nRatio, this._aPointData[0][0] * nRatio, this._aPointData[0][1] * nRatio, collie.raphaelDashArray[htInfo.dashArray], nStrokeWidth);
6720		}
6721		
6722		if (htInfo.closePath) {
6723			oContext.closePath();
6724		}
6725		
6726		if (!htInfo.dashArray && (htInfo.fillColor || htInfo.fillImage)) {
6727			oContext.fill();
6728		}
6729		
6730		if (htInfo.strokeWidth) {
6731			oContext.stroke();
6732		}
6733		
6734		oContext.restore();
6735	},
6736	
6737	/**
6738	 * Returns information of The Class as String
6739	 * 
6740	 * @return {String}
6741	 */
6742	toString : function () {
6743		return "Polyline" + (this._htOption.name ? " " + this._htOption.name : "")+ " #" + this.getId() + (this.getImage() ? "(image:" + this.getImage().src + ")" : "");
6744	}
6745}, collie.DisplayObject);
6746/**
6747 * Text
6748 * - 말줄임은 Canvas일 때만 된다. DOM은 미구현
6749 * TODO Text는 말줄임과 자동 줄바꿈 때문에 모바일에서 사용하면 굉장히 느리다. WebWorker를 쓸 수 있는지 확인해 봐야 할 것
6750 * 
6751 * @class collie.Text
6752 * @extends collie.DisplayObject
6753 * @param {Object} [htOption]
6754 * @param {Object} [htOption.fontFamily='Arial'] 글꼴
6755 * @param {Object} [htOption.fontWeight=''] 스타일, bold 면 진하게
6756 * @param {Object} [htOption.fontSize=12] 크기 (px)
6757 * @param {Object} [htOption.fontColor='#000000'] 글꼴 색상
6758 * @param {Object} [htOption.lineHeight="auto"] 라인 간격 (px), auto면 자동으로 맞춰짐
6759 * @param {Object} [htOption.textAlign='left'] 텍스트 정렬  left, center, right
6760 * @param {Object} [htOption.padding="0 0 0 0"] 텍스트 패딩 (px) top right bottom left
6761 * @param {Object} [htOption.ellipsisMaxLine=0] 최대 라인 수. 라인 수를 넘을 경우 말줄임 함. (0이면 사용 안함)
6762 * @param {Object} [htOption.ellipsisString='...'] 말줄임 할 때 대체할 텍스트
6763 * @param {Object} [htOption.useEllipsis=false] 말줄임 사용 여부
6764 * @example
6765 * 기본적인 사용법
6766 * <code>
6767 * var oText = new collie.Text({
6768 * 	width : 100, // 너비와 높이를 반드시 지정해야 합니다.
6769 * 	height : 100,
6770 * 	x : 0,
6771 * 	y : 0,
6772 * 	fontColor : "#000000"
6773 * }).text("테스트 입니다");
6774 * </code>
6775 */
6776collie.Text = collie.Class(/** @lends collie.Text.prototype */{
6777	/**
6778	 * @constructs
6779	 */
6780	$init : function (htOption) {
6781		this._sText = "";
6782		this.option({
6783			fontFamily : 'Arial', // 글꼴 스타일
6784			fontWeight : '', // bold
6785			fontSize : 12, // px
6786			fontColor : '#000000', // 글꼴 색상
6787			lineHeight : "auto", // 라인 간격, px null이면 auto면 자동
6788			textAlign : 'left', // 텍스트 정렬 left, center, right
6789			padding : "0 0 0 0", // 텍스트 패딩
6790			ellipsisMaxLine : 0, // 최대 라인 수. 지정하면 말줄임 함
6791			ellipsisString : '...', // 말줄임 텍스트
6792			useEllipsis : false, // 말줄임 사용 여부
6793			useCache : true // useCache 기본값 true
6794		}, null, true /* Don't overwrite options */);
6795		
6796		this._elText = null;
6797		this._nTextWidth = 0;
6798		this._nRatio = 1;
6799		this._aCallbackTextWidth = [];
6800	},
6801	
6802	_initElement : function () {
6803		if (this._elText === null) {
6804			this._elText = document.createElement("div");
6805			this._elText.style.display = "inline";
6806			this.getDrawing().getItemElement().appendChild(this._elText);
6807		}
6808	},
6809	
6810	/**
6811	 * Delegate
6812	 * @private
6813	 */
6814	onCanvasDraw : function (oEvent) {
6815		this._nRatio = this._sRenderingMode === "canvas" && this._bRetinaDisplay ? 2 : 1;
6816		this._oContext = oEvent.context;
6817		var nMaxWidth = this._getMaxWidth();
6818		this._oContext.font = this._getFontText();
6819		this._oContext.fillStyle = this._htOption.fontColor;
6820		this._oContext.textBaseline = "top";
6821		this._fillTextMultiline(this._wordWrap(nMaxWidth).split("\n"), oEvent.x, oEvent.y);
6822		this._triggerGetTextWidth();
6823	},
6824	
6825	/**
6826	 * Delegate
6827	 * @private
6828	 */
6829	onDOMDraw : function (oEvent) {
6830		this._initElement();
6831		var oDrawing = this.getDrawing();
6832		var el = oEvent.element;
6833		var sText = this._sText.replace(/\n/g, "<br />");
6834		var elStyle = el.style;
6835		elStyle.font = this._getFontText();
6836		elStyle.color = this._htOption.fontColor;
6837		elStyle.padding = this._getPadding().replace(/ /g, "px ") + "px";
6838		elStyle.width = this._getMaxWidth() + "px";
6839		elStyle.height = this._getMaxHeight() + "px";
6840		elStyle.lineHeight = this._getLineHeight() + "px";
6841		elStyle.textAlign = this._htOption.textAlign;
6842		
6843		if (this._elText.innerHTML != sText) {
6844			this._elText.innerHTML = sText;
6845		}
6846		
6847		this.unsetChanged();
6848		this._getDOMTextWidth();
6849		this._triggerGetTextWidth();
6850	},
6851	
6852	_getDOMTextWidth : function () {
6853		if (this._elText !== null) {
6854			this._nTextWidth = this._elText.offsetWidth;
6855		}
6856	},
6857	
6858	_getFontText : function () {
6859		return this._htOption.fontWeight + " " + (this._htOption.fontSize * this._nRatio) + "px " + this._htOption.fontFamily;
6860	},
6861	
6862	_getLineHeight : function () {
6863		return this._htOption.lineHeight === "auto" ? (this._htOption.fontSize * this._nRatio) : this._htOption.lineHeight * this._nRatio;
6864	},
6865	
6866	/**
6867	 * 여러 줄의 텍스트를 연달아 쓴다
6868	 * 
6869	 * @private
6870	 * @param {Array} aText 한 배열 당 한 줄
6871	 */
6872	_fillTextMultiline : function (aText, nX, nY) {
6873		var nLeft = this._getPadding("left");
6874		var nMaxLine = this._htOption.ellipsisMaxLine;
6875		this._nTextWidth = 0;
6876		
6877		for (var i = 0; i < aText.length; i++) {
6878			if (nMaxLine && i >
6878= nMaxLine - 1) {
6879				// 말줄임이 필요하면
6880				if (aText.length > nMaxLine) {
6881					aText[i] = this._insertEllipsisText(aText[i]);
6882					aText.splice(i + 1, aText.length - (i + 1)); // 멈춤
6883				}
6884			}
6885			
6886			var nTextWidth = this._oContext.measureText(aText[i]).width;
6887			
6888			if (this._htOption.textAlign === "center") {
6889				nLeft = this._getMaxWidth() / 2 - nTextWidth / 2 + this._getPadding("left");
6890			} else if (this._htOption.textAlign === "right") {
6891				nLeft = ((this._htOption.width * this._nRatio) - this._getPadding("right")) - nTextWidth;
6892			}
6893			
6894			this._oContext.fillText(aText[i], nX + nLeft, nY + this._getTopPosition(i + 1));
6895			this._nTextWidth = Math.max(this._nTextWidth, nTextWidth);
6896		}
6897	},
6898	
6899	_getMaxWidth : function () {
6900		return (this._htOption.width * this._nRatio) - (this._getPadding("left") + this._getPadding("right"));
6901	},
6902	
6903	_getMaxHeight : function () {
6904		return (this._htOption.height * this._nRatio) - (this._getPadding("top") + this._getPadding("bottom"));
6905	},
6906	
6907	/**
6908	 * 시작 top 위치를 반환
6909	 * 
6910	 * @private
6911	 * @param {Number} nLine 라인번호, 1부터 시작
6912	 */
6913	_getTopPosition : function (nLine) {
6914		return this._getLineHeight() * (nLine - 1) + this._getPadding("top");
6915	},
6916	
6917	/**
6918	 * 해당 포지션의 패딩 값을 반환한다
6919	 * 
6920	 * @param {String} sPositionName top, right, bottom, left, 값이 없으면 전체 문자열을 반환, 단위는 쓰지 않는다. px
6921	 * @return {Number|String}
6922	 * @private
6923	 */
6924	_getPadding : function (sPositionName) {
6925		var sPadding = this._htOption.padding || "0 0 0 0";
6926		var aPadding = sPadding.split(" ");
6927		
6928		for (var i = 0, len = aPadding.length; i < len; i++) {
6929			aPadding[i] = parseInt(aPadding[i], 10) * this._nRatio;
6930		}
6931		
6932		switch (sPositionName) {
6933			case "top" :
6934				return aPadding[0];
6935				
6936			case "right" :
6937				return aPadding[1];
6938				
6939			case "bottom" :
6940				return aPadding[2];
6941				
6942			case "left" :
6943				return aPadding[3];
6944				
6945			default :
6946				return aPadding.join(" ");
6947		}
6948	},
6949	
6950	/**
6951	 * 말줄임된 텍스트를 반환
6952	 * @private
6953	 */
6954	_insertEllipsisText : function (sText) {
6955		var nWidth = this._getMaxWidth();
6956		var sEllipsizedText = '';
6957		
6958		for (var i = sText.length; i > 0; i--) {
6959			sEllipsizedText = sText.substr(0, i) + this._htOption.ellipsisString;
6960			
6961			if (this._oContext.measureText(sEllipsizedText).width <= nWidth) {
6962				return sEllipsizedText;
6963			}
6964		}
6965		
6966		return sText;
6967	},
6968	
6969	/**
6970	 * 자동 줄바꿈
6971	 * - 재귀 호출
6972	 *
6973	 * @ignore
6974	 * @param {Number} nWidth 줄바꿈 될 너비
6975	 * @param {String} sText 텍스트, 재귀호출 되면서 나머지 길이의 텍스트가 들어간다
6976	 * @return {String} 줄바꿈된 테스트
6977	 */
6978	_wordWrap : function (nWidth, sText) {
6979		var sOriginalText = sText || this._sText;
6980		var nCount = 1;
6981		
6982		// 원본 문자가 없으면
6983		if (!sOriginalText) {
6984			return '';
6985		}
6986		
6987		sText = sOriginalText.substr(0, 1);
6988		
6989		// 첫자부터 시작해서 해당 너비까지 도달하면 자르기
6990		while (this._oContext.measureText(sText).width <= nWidth) {
6991			nCount++;
6992			
6993			// 더이상 못자르면 반환
6994			if (nCount > sOriginalText.length) {
6995				return sText;
6996			}
6997			
6998			// 자르기
6999			sText = sOriginalText.substr(0, nCount);
7000			
7001			// 줄바꿈 문자면 지나감
7002			if (sOriginalText.substr(nCount - 1, 1) === "\n") {
7003				break;
7004			}
7005		}
7006		
7007		nCount = Math.max(1, nCount - 1);
7008		sText = sOriginalText.substr(0, nCount);
7009		
7010		// 다음 문자가 줄바꿈문자면 지나감
7011		if (sOriginalText.substr(nCount, 1) === "\n") {
7012			nCount++;
7013		}
7014		
7015		// 뒤에 더 남아있다면 재귀 호출
7016		if (sOriginalText.length > nCount) {
7017			sText += "\n" + (this._wordWrap(nWidth, sOriginalText.substr(nCount)));
7018		}
7019		
7020		return sText;
7021	},
7022	
7023	/**
7024	 * 텍스트를 쓴다
7025	 * Write text
7026	 * 
7027	 * @param {String} sText 출력할 데이터 text data
7028	 * @return {collie.Text} 메서드 체이닝을 위해 자기 자신을 반환. return self instance for method chaining
7029	 */
7030	text : function (sText) {
7031		this._nTextWidth = 0;
7032		this._aCallbackTextWidth = [];
7033		this._sText = sText.toString();
7034		this.setChanged();
7035		return this;
7036	},
7037	
7038	/**
7039	 * 텍스트 최대 너비를 반환, 그려지기 전에는 반환이 되지 않기 때문에 callback 함수를 넣어 그려진 후에 값을 받아올 수 있다
7040	 * 콜백 함수 첫번째 인자가 너비 값
7041	 * @param {Function} fCallback
7042	 * @return {Number} 텍스트 최대 너비
7043	 */
7044	getTextWidth : function (fCallback) {
7045		if (fCallback) {
7046			this._aCallbackTextWidth.push(fCallback);
7047		}
7048		
7049		if (this._nTextWidth) {
7050			this._triggerGetTextWidth();
7051			return this._nTextWidth / this._nRatio;
7052		}
7053	},
7054	
7055	_triggerGetTextWidth : function () {
7056		if (this._aCallbackTextWidth.length > 0) {
7057			for (var i = 0, len = this._aCallbackTextWidth.length; i < len; i++) {
7058				this._aCallbackTextWidth[i](this._nTextWidth / this._nRatio);
7059			}
7060			
7061			this._aCallbackTextWidth = [];
7062		}
7063	},
7064	
7065	/**
7066	 * 문자열로 클래스 정보 반환
7067	 * 
7068	 * @return {String}
7069	 */
7070	toString : function () {
7071		return "Text" + (this._htOption.name ? " " + this._htOption.name : "")+ " #" + this.getId() + (this.getImage() ? "(image:" + this.getImage().src + ")" : "");
7072	}
7073}, collie.DisplayObject);
7074/**
7075 * 애니메이션 부모 클래스
7076 * 
7077 * @class collie.Animation
7078 * @extends collie.Component
7079 * @param {Function} fCallback 타이머 콜백 함수
7080 * @param {Number} nDuration 타이머 실행 시간, 지연 시간 (ms)
7081 * @param {Object} htOption 설정
7082 * @param {Boolean} htOption.useAutoStart TimerList에 추가될 때 자동으로 시작 된다
7083 * @param {Function} [htOption.on이벤트명] onComplete와 같이 이벤트명을 사용해서 attach를 직접하지 않고 옵션으로 할 수 있다
7084 */
7085collie.Animation = collie.Class(/** @lends collie.Animation.prototype */{
7086	/**
7087	 * @constructs
7088	 */
7089	$init : function (fCallback, nDuration, htOption) {
7090		this._nId = ++collie.Animation._idx;
7091		this._bIsPlaying = false;
7092		this._fCallback = fCallback;
7093		this._oTimerList = null;
7094		
7095		// AnimationQueue의 경우 nDuration자리에 htOption이 들어간다
7096		this.option("useAutoStart", true);
7097		this.option((typeof nDuration === "object" ? nDuration : htOption) || {});
7098		this.setDuration(nDuration);
7099		
7100		// 이벤트 핸들러 할당
7101		this.setOptionEvent(htOption);
7102	},
7103	
7104	/**
7105	 * Option 설정을 event로 만듦
7106	 * @private
7107	 */
7108	setOptionEvent : function (htOption) {
7109		if (htOption) {
7110			for (var i in htOption) {
7111				if (i.toString().indexOf("on") === 0) {
7112					this.attach(i.toString().replace(/^on/, '').toLowerCase(), htOption[i]);
7113				}
7114			}
7115		}
7116	},
7117	
7118	/**
7119	 * Callback을 형태에 맞게 실행
7120	 * 
7121	 * @private
7122	 * @param {Object} htParam
7123	 */
7124	triggerCallback : function (htParam) {
7125		// callback에 DisplayObject 객체를 넘길 경우
7126		if (typeof this._fCallback !== "function" && this._htOption.set) {
7127			var htOption = {};
7128			
7129			// 배열 값 처리
7130			if (this._htOption.set instanceof Array) {
7131				for (var i = 0, len = this._htOption.set.length; i < len; i++) {
7132					htOption[this._htOption.set[i]] = (htParam.value instanceof Array) ? htParam.value[i] : htParam.value;
7133				}
7134			} else {
7135				htOption[this._htOption.set] = htParam.value;
7136			}
7137			
7138			// 실행
7139			if (this._fCallback instanceof Array) {
7140				for (var j = 0, len = this._fCallback.length; j < len; j++) {
7141					this._fCallback[j].set(htOption);
7142				}
7143			} else {
7144				this._fCallback.set(htOption);
7145			}
7146		} else if (this._fCallback) {
7147			this._fCallback(htParam);
7148		}
7149	},
7150	
7151	/**
7152	 * Duration을 설정
7153	 * 
7154	 * @param {Number|String} nDuration 실행 시간, 지연 시간 설정 (ms)
7155	 */
7156	setDuration : function (nDuration) {
7157		this._nDuration = parseInt(nDuration, 10);
7158	},
7159	
7160	/**
7161	 * Duration을 반환
7162	 * 
7163	 * @return {Number} (ms)
7164	 */
7165	getDuration : function () {
7166		return this._nDuration;
7167	},
7168	
7169	/**
7170	 * TimerList에 추가될 때 알려 줌. stop할 때 목록에서 빼기 위함
7171	 * 
7172	 * @param {collie.TimerList}
7173	 * @private
7174	 */
7175	setTimerList : function (oTimerList) {
7176		this._oTimerList = oTimerList;
7177		
7178		if (this._htOption.useAutoStart) {
7179			this.start();
7180		}
7181	},
7182	
7183	/**
7184	 * 애니메이션 인스턴스를 식별하는 아이디를 반환
7185	 * 
7186	 * @private
7187	 * @return {Number} 아이디 (1...)
7188	 */
7189	getId : function () {
7190		return this._nId;
7191	},
7192	
7193	/**
7194	 * 애니메이션을 실행
7195	 * 
7196	 * @abstract
7197	 * @param {Number} [nCurrentFrame] 현재 렌더러 프레임, 값이 없으면 자동으로 현재 렌더러 프레임을 가져 온다
7198	 * @param {Number} [nDrameDuration] 진행된 프레임 시간(ms)
7199	 */
7200	run : function (nCurrentFrame, nFrameDuration) {
7201		throw new Error('abstract method');
7202	},
7203	
7204	/**
7205	 * 설정 값을 초기화 할 때 사용
7206	 * 
7207	 * @abstract
7208	 */
7209	reset : function () {
7210		throw new Error('abstract method');
7211	},
7212	
7213	/**
7214	 * 애니메이션을 정지
7215	 * @example
7216	 * // stop/start
7217	 * var timer = collie.Timer.repeat(function () {}, 1000);
7218	 * timer.stop();
7219	 * timer.start();
7220	 * 
7221	 * @param {Boolean}
7221 bSkipEvent 이벤트를 발생하지 않는다
7222	 */
7223	stop : function (bSkipEvent) {
7224		if (this.isPlaying()) {
7225			if (this._oTimerList !== null) {
7226				this._oTimerList.remove(this);
7227			}
7228			
7229			this._bIsPlaying = false;
7230			this.reset();
7231			
7232			/**
7233			 * 타이머를 정지할 때 발생. 정상적으로 complete된 경우에는 발생하지 않는다
7234			 * @name collie.Animation#stop
7235			 * @event
7236			 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
7237			 */
7238			if (!bSkipEvent) {
7239				this.fireEvent("stop");
7240			}
7241		}
7242	},
7243	
7244	/**
7245	 * 애니메이션을 일시정지
7246	 * @example
7247	 * // pause/start
7248	 * var timer = collie.Timer.repeat(function () {}, 1000);
7249	 * timer.pause();
7250	 * timer.start();
7251	 */
7252	pause : function () {
7253		if (this.isPlaying()) {
7254			this._bIsPlaying = false;
7255			
7256			/**
7257			 * 타이머를 일시 정지할 때 발생
7258			 * @name collie.Animation#pause
7259			 * @event
7260			 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
7261			 */
7262			this.fireEvent("pause");
7263			
7264			if (this._oTimerList !== null) {
7265				this._oTimerList.remove(this);
7266			}
7267		}
7268	},
7269
7270	/**
7271	 * 정지 상태인 타이머를 다시 실행
7272	 */
7273	start : function () {
7274		if (!this.isPlaying()) {
7275			this._bIsPlaying = true;
7276			
7277			if (this._oTimerList !== null) {
7278				this._oTimerList.add(this);
7279			}
7280			
7281			/**
7282			 * 타이머를 실행할 때 발생. Timer를 이용해서 생성할 때는 생성하는 순간 시작 상태이다.
7283			 * @name collie.Animation#start
7284			 * @event
7285			 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
7286			 */
7287			this.fireEvent("start");
7288		}
7289	},
7290	
7291	/**
7292	 * 타이머가 진행 중인지 여부를 반환
7293	 * 
7294	 * @return {Boolean} true면 실행 중
7295	 */
7296	isPlaying : function () {
7297		return this._bIsPlaying;
7298	},
7299	
7300	/**
7301	 * 애니메이션이 완료 됐을 때 실행
7302	 */
7303	complete : function () {
7304		if (this.isPlaying()) {
7305			if (this._fCallbackComplete) {
7306				this._fCallbackComplete();
7307			}
7308			
7309			// complete 이벤트 발생 전에 멈춤
7310			this.stop(true);
7311			
7312			/**
7313			 * 타이머가 정상적으로 종료되면 발생, repeat나 cycle의 loop가 0과 같이 지속적으로 반복되는 타이머에서는 일어나지 않는다
7314			 * @name collie.Animation#complete
7315			 * @event
7316			 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
7317			 */
7318			this.fireEvent("complete");
7319		}
7320	}
7321	
7322	/**
7323	 * 현재 타이머를 제거한다
7324	 * 
7325	 * @name collie.Animation#remove
7326	 * @deprecated start, stop할 때 List에 추가되고 제거되기 때문에 별도로 remove를 할 필요가 없어졌다.
7327	 */
7328}, collie.Component);
7329
7330/**
7331 * @private
7332 */
7333collie.Animation._idx = 0;
7334/**
7335 * collie.Effect를 사용한 Transition 타이머
7336 * <a href="../tutorial/timer_transition.html" target="_blank">튜토리얼 보기</a>
7337 *
7338 * @example
7339 * <code>
7340 * 여러 개의 값으로 트랜지션
7341 * collie.Timer.transition(function (oEvent) {
7342 * 	oDisplayObject.set("opacity", oEvent.value[0]);
7343 *  oDisplayObject.set("x", oEvent.value[1]);
7344 * }, 1000, {
7345 * 	from : [1, 100],
7346 *  to : [0, 300]
7347 * });
7348 * </code>
7349 * 
7350 * @example
7351 * DisplayObject를 callback으로 사용해서 여러 속성을 변경하는 방법
7352 * <code>
7353 * collie.Timer.transition(oDisplayObject, 1000, {
7354 * 	from : [10, 10], // from 은 생략 가능, 생략하면 현재 값이 자동으로 입력 됨
7355 * 	to : [100, 200],
7356 * 	set : ["x", "y"]
7357 * });  
7358 * </code>
7359 * 하지만 이 때에는 DisplayObject의 move 메서드를 사용하는 것이 좋음
7360 * 
7361 * @example
7362 * 여러 개의 DisplayObject를 한꺼번에 실행할 수도 있음
7363 * <code>
7364 * collie.Timer.transition([oDisplayObjectA, oDisplayObjectB], 1000, {
7365 * 	from : 0, // 이 때에는 from 생략 불가능
7366 * 	to : 1,
7367 * 	set : "opacity"
7368 * });
7369 * </code>
7370 * 
7371 * @see collie.Timer
7372 * @class collie.AnimationTransition
7373 * @extends collie.Animation
7374 * @param {Function|collie.DisplayObject|Array} fCallback 실행될 콜백 함수, DisplayObject를 넣게 되면 해당 객체에 관한 내용만 변경함. htOption의 set 참조.
7375 * @param {collie.AnimationCycle} fCallback.timer 현재 타이머 인스턴스
7376 * @param {Number} fCallback.frame 현재 프레임
7377 * @param {Number} fCallback.duration 타이머에 설정된 duraiton 값
7378 * @param {Number} fCallback.cycle 반복 횟수
7379 * @param {Number} fCallback.runningTime 타이머 시작 후 실행된 시간 (ms)
7380 * @param {Number|Array} fCallback.value 적용할 값. from, to 값이 배열일 경우 이 값도 배열로 반환
7381 * @param {Number|Array} fCallback.from 시작 값, 시작 값을 입력하지 않고 fCallback에 DisplayObject를 넣으면 해당 객체의 현재 값이 자동으로 입력됨
7382 * @param {Number|Array} fCallback.to 끝 값
7383 * @param {Number} nDuration 실행 시간
7384 * @param {Object} htOption 설정
7385 * @param {Number|Array} htOption.from 시작 값(배열로 넣을 수 있음)
7386 * @param {Number|Array} htOption.to 끝 값(배열로 넣을 수 있음)
7387 * @param {Number} [htOption.loop=1] 반복 횟수
7388 * @param {collie.Effect} [htOption.effect=collie.Effect.linear] 효과 함수
7389 * @param {String|Array} [htOption.set] fCallback에 DisplayObject를 넣을 경우 set을 이용해서 특정 값을 변경한ë
7389‹¤. 배열로 넣을 경우 여러 속성을 변경할 수 있다
7390 * @see collie.Effect
7391 */
7392collie.AnimationTransition = collie.Class(/** @lends collie.AnimationTransition.prototype */{
7393	/**
7394	 * @constructs
7395	 */
7396	$init : function (fCallback, nDuration, htOption) {
7397		this.option({
7398			from : null, // 시작 값(배열로 넣을 수 있음)
7399			to : null, // 끝 값(배열로 넣을 수 있음)
7400			set : "",
7401			loop : 1,
7402			effect : collie.Effect.linear // 이펙트 함수
7403		});
7404		this._htCallback = {};
7405		this.option(htOption || {});
7406		var fReset = this.reset.bind(this);
7407		this.optionSetter("from", fReset);
7408		this.optionSetter("to", fReset);
7409		this._nCount = 0;
7410		this._nCountCycle = 0;
7411		this._nFrameAtRunLastest = null;
7412		this._nRunningTime = null;
7413		this._bIsArrayValue = false;
7414	},
7415	
7416	/**
7417	 * 시작할 때 실행되는 메서드
7418	 * @override
7419	 */
7420	start : function () {
7421		// 시작 값이 없을 떄 객체의 현재 값을 입력
7422		if (this._htOption.from === null && typeof this._fCallback !== "function") {
7423			this._setDefaultFromValues();
7424		}
7425		
7426		if (this._nFrameAtRunLastest === null) {
7427			this.reset();
7428		}
7429		
7430		this.constructor.$super.start.call(this);
7431	},
7432	
7433	/**
7434	 * @private
7435	 */
7436	_setDefaultFromValues : function () {
7437		var vFrom = null;
7438		
7439		if (this._htOption.set) {
7440			if (this._htOption.set instanceof Array) {
7441				vFrom = [];
7442				for (var i = 0, len = this._htOption.set.length; i < len; i++) {
7443					vFrom.push(this._fCallback.get(this._htOption.set[i]));
7444				}
7445			} else {
7446				vFrom = this._fCallback.get(this._htOption.set)
7447			}
7448			
7449			this.option("from", vFrom);
7450		}
7451	},
7452	
7453	/**
7454	 * 값을 초기화
7455	 */
7456	reset : function () {
7457		this._nFrameAtRunLastest = null;
7458		this._nRunningTime = null;
7459		this._nValue = this._htOption.from;
7460		this._bIsArrayValue = this._htOption.from instanceof Array;
7461		this._nCount = 0;
7462		this._nCountCycle = 0;
7463		
7464		// 값이 배열일 경우 처리
7465		if (this._bIsArrayValue) {
7466			this._fEffect = [];
7467			var fEffect = null;
7468			
7469			for (var i = 0, len = this._htOption.from.length; i < len; i++) {
7470				fEffect = (this._htOption.effect instanceof Array) ? this._htOption.effect[i] : this._htOption.effect; 
7471				this._fEffect[i] = fEffect(this._htOption.from[i], this._htOption.to[i]);
7472			}
7473		} else {
7474			this._fEffect = this._htOption.effect(this._htOption.from, this._htOption.to);
7475		}
7476	},
7477	
7478	/**
7479	 * 현재 값을 설정
7480	 * 
7481	 * @param {Variables} vValue
7482	 */
7483	setValue : function (vValue) {
7484		this._nValue = vValue;
7485	},
7486	
7487	/**
7488	 * 현재 값을 반환
7489	 * 
7490	 * @return {Variables}
7491	 */
7492	getValue : function () {
7493		return this._nValue;
7494	},
7495	
7496	/**
7497	 * 애니메이션을 실행
7498	 * 
7499	 * @param {Number} [nCurrentFrame] 현재 렌더러 프레임, 값이 없으면 자동으로 현재 렌더러 프레임을 가져 온다
7500	 * @param {Number} [nFrameDuration] 진행된 프레임 시간(ms)
7501	 */
7502	run : function (nCurrentFrame, nFrameDuration) {
7503		if (nCurrentFrame === undefined) {
7504			nCurrentFrame = collie.Renderer.getInfo().frame;
7505		}
7506		
7507		// 렌더러가 stop 된 경우
7508		if (this._nFrameAtRunLastest > nCurrentFrame) {
7509			this.reset();
7510			return;
7511		}
7512		
7513		// 시작 프레임 저장
7514		if (this._nFrameAtRunLastest === null) {
7515			this._nFrameAtRunLastest = nCurrentFrame;
7516			this._nRunningTime = 0;
7517			nFrameDuration = 0;
7518		}
7519		
7520		this._nRunningTime += nFrameDuration;
7521		this._nCount++;
7522		
7523		// 시간이 지났으면 멈춤
7524		if (this._nRunningTime >= this._nDuration) {
7525			this._nCountCycle++;
7526			
7527			// 끝나는 값이 아니면 끝나는 값으로 만듦(한번 더 실행), 루프의 마지막일 때만 보정함.
7528			if (!this._isEndValue() && this._htOption.loop && this._htOption.loop <= this._nCountCycle) {
7529				this._setEndValue();
7530			} else if (!this._htOption.loop || this._htOption.loop > this._nCountCycle) {
7531				/**
7532				 * loop가 있을 경우 트랜지션이 한번 끝났을 때 발생
7533				 * @name collie.AnimationTransition#end
7534				 * @event
7535				 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
7536				 */
7537				this.fireEvent("end");
7538				this._nFrameAtRunLastest = nCurrentFrame;
7539				this._nRunningTime = this._nRunningTime - this._nDuration; // loop면 처음부터 다시 시작이 아니라 이어서 시작
7540				this._nValue = this._htOption.from;
7541				this._transitionValue(this._nRunningTime);
7542			} else {
7543				/**
7544				 * 트랜지션이 끝난 후 발생
7545				 * @name collie.AnimationTransition#complete
7546				 * @event
7547				 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
7548				 */
7549				this.complete();
7550				return;
7551			}
7552		} else if (this._nRunningTime > 0) {
7553			this._transitionValue(this._nRunningTime);
7554		}
7555		
7556		// 객체 재활용
7557		this._htCallback.timer = this;
7558		this._htCallback.frame = nCurrentFrame;
7559		this._htCallback.duration = this._nDuration;
7560		this._htCallback.cycle = this._nCountCycle;
7561		this._htCallback.runningTime = this._nRunningTime;
7562		this._htCallback.from = this._htOption.from;
7563		this._htCallback.to = this._htOption.to;
7564		this._htCallback.value = this._nValue; // 값이 배열이면 이것도 배열로 반환됨
7565		this.triggerCallback(this._htCallback);
7566		
7567		if (this._nRunningTime > 0) {
7568			this._nFrameAtRunLastest = nCurrentFrame;
7569		}
7570	},
7571	
7572	/**
7573	 * 현재 프레임 값을 받아 현재 값을 transition된 값으로 변경 한다
7574	 * @private
7575	 * @param {Number} nCurrentRunningTime 현재 진행된 시간(ms)
7576	 */
7577	_transitionValue : function (nCurrentRunningTime) {
7578		if (this._bIsArrayValue) {
7579			this._nValue = [];
7580			
7581			for (var i = 0, len = this._htOption.from.length; i < len; i++) {
7582				this._nValue[i] = parseFloat(this._fEffect[i](Math.max(0, Math.min(1, nCurrentRunningTime / this._nDuration))));
7583			}
7584		} else {
7585			this._nValue = parseFloat(this._fEffect(Math.max(0, Math.min(1, nCurrentRunningTime / this._nDuration))));
7586		}
7587	},
7588	
7589	/**
7590	 * 끝 값인지 여부를 반환
7591	 * @private
7592	 * @return {Boolean} true면 끝 값
7593	 */
7594	_isEndValue : function () {
7595		if (this._bIsArrayValue) {
7596			for (var i = 0, len = this._htOption.to.length; i < len; i++) {
7597				if (this._nValue[i] !== parseFloat(this._fEffect[i](1))) {
7598					return false;
7599				}
7600			}
7601			
7602			return true;
7603		} else {
7604			return this._nValue === parseFloat(this._fEffect(1));
7605		}
7606	},
7607	
7608	/**
7609	 * 현재 값을 끝 값으로 설정 한다
7610	 * @private
7611	 * @param {Number} nValue
7612	 */
7613	_setEndValue : function () {
7614		if (this._bIsArrayValue) {
7615			for (var i = 0, len = this._htOption.to.length; i < len; i++) {
7616				this._nValue[i] = parseFloat(this._fEffect[i](1));
7617			}
7618		} else {
7619			this._nValue = parseFloat(this._fEffect(1));
7620		}
7621	}
7622}, collie.Animation);
7623
7624/**
7625 * 이벤트 효과 함수
7626 * @namespace
7627 * @name collie.Effect
7628 */
7629/**
7630 * 특정 시간 간격으로 계속 반복되는 타이머
7631 * <a href="../tutorial/timer_repeat.html" target="_blank">튜토리얼 보기</a>
7632 * 
7633 * timeline	---------------------------------&gt;
7634 * action	* duration * duration * duration *
7635 * @see collie.Timer
7636 * @class collie.AnimationRepeat
7637 * @extends collie.Animation
7638 * @param {Function} fCallback 실행될 콜백 함수
7639 * @param {collie.AnimationCycle} fCallback.timer 현재 타이머 인스턴스
7640 * @param {Number} fCallback.frame 현재 프레임
7641 * @param {Number} fCallback.duration 타이머에 설정된 duraiton 값
7642 * @param {Number} fCallback.count 실행 횟수
7643 * @param {Number} fCallback.skippedCount 지나간 실행 횟수
7644 * @param {Number} fCallback.runningTime 타이머 시작 후 실행된 시간 (ms)
7645 * @param {Number} nDuration 시간 간격 ms
7646 * @param {Object} [htOption]
7647 * @param {Number} [htOption.beforeDelay=0] 시작되기 전에 지연시간(ms)
7648 * @param {Number} [htOption.loop=0] 반복 횟수(0이면 무한 반복, complete 이벤트가 일어나지 않는다)
7649 * @param {Number} [htOption.useRealTime=true] SkippedFrame을 적용해서 count 값을 보정한다
7650 */
7651collie.AnimationRepeat = collie.Class(/** @lends collie.AnimationRepeat.prototype */{
7652	/**
7653	 * @constructs
7654	 */
7655	$init : function (fCallback, nDuration, htOption) {
7656		this.option({
7657			beforeDelay : 0,
7658			afterDelay : 0,
7659			loop : 0,
7660			useRealTime : true
7661		});
7662		this.option(htOption || {});
7663		this.reset();
7664		this.setDuration(nDuration);
7665		this._nFrameAtRunLastest = null;
7666	},
7667
7668	/**
7669	 * Duration을 설정
7670	 * Repeat는 Renderer의 Duration보다 짧게 실행할 수 없기 때문에 값을 보정한다
7671	 * 
7672	 * @param {Number} nDuration 실행 시간, 지연 시간 설정 (ms)
7673	 */
7674	setDuration : function (nDuration) {
7675		nDuration = parseInt(nDuration, 10);
7676		
7677		if (nDuration < collie.Renderer.getDuration()) {
7678			nDuration = collie.Renderer.getDuration();
7679		}
7680		
7681		this._nDuration = nDuration;
7682	},
7683	
7684	/**
7685	 * 값을 초기화
7686	 */
7687	reset : function () {
7688		this._nCount = 0;
7689		this._nFrameAtRunLastest = null;
7690		this._nRunningTime = null;
7691		this._nRunLastestTime = null;
7692		this._nBeforeDelay = this._htOption.beforeDelay;
7693	},
7694	
7695	/**
7696	 * 애니메이션을 실행
7697	 * 
7698	 * @param {Number} [nCurrentFrame] 현재 렌더러 프레임, 값이 없으면 자동으로 현재 렌더러 프레임을 가져 온다
7699	 * @param {Number} [nFrameDuration] 진행된 프레임 시간(ms)
7700	 */
7701	run : function (nCurrentFrame, nFrameDuration) {
7702		if (nCurrentFrame === undefined) {
7703			nCurrentFrame = collie.Renderer.getInfo().frame;
7704		}
7705		
7706		// stop 된 경우
7707		if (this._nFrameAtRunLastest > nCurrentFrame) {
7708			this.reset();
7709			return;
7710		}
7711		
7712		// 시작되지 않았을 때 시작 시점 기록
7713		if (this._nFrameAtRunLastest === null) {
7714			this._nFrameAtRunLastest = nCurrentFrame;
7715			this._nRunningTime = 0;
7716			this._nRunLastestTime = 0;
7717			nFrameDuration = 0;
7718		}
7719		
7720		this._nRunningTime += nFrameDuration;
7721		var nSkippedCount = Math.max(1, Math.floor((this._nRunningTime - this._nRunLastestTime) / this._nDuration)) - 1;
7722		
7723		// 시작 지연시간
7724		if (this._nCount === 0 && this._nBeforeDelay) {
7725			// 끝날 때 되면 처리
7726			if (this._nRunLastestTime + this._nBeforeDelay <= this._nRunningTime) {
7727				this.reset();
7728				this._nBeforeDelay = 0;
7729			}
7730			return;
7731		}
7732		
7733		// 실행되어야 할 시간이 지났다면 실행
7734		if (this._nRunningTime === 0 || this._nRunLastestTime + this._nDuration <= this._nRunningTime) {
7735			this._nCount += this._htOption.useRealTime ? 1 + nSkippedCount : 1;
7736			this._fCallback({
7737				timer : this,
7738				frame : nCurrentFrame,
7739				duration : this._nDuration,
7740				count : this._nCount,
7741				skippedCount : nSkippedCount,
7742				runningTime : this._nRunningTime
7743			});
7744			
7745			if (this._htOption.loop && this._htOption.loop <= this._nCount) {
7746				/**
7747				 * 계획된 모든 애니메이션과 반복 횟수가 끝나면 발생. loop=0으로 설정하면 발생하지 않는다.
7748				 * @name collie.AnimationRepeat#complete
7749				 * @event
7750				 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
7751				 */
7752				this.complete();
7753				return;
7754			}
7755			
7756			this._nFrameAtRunLastest = nCurrentFrame;
7757			this._nRunLastestTime = this._nRunningTime;
7758		}
7759	}
7760}, collie.Animation);
7761/**
7762 * 특정 시간 동안 실행된 후 대기 시간 이후에 다시 실행되는 싸이클 애니메이션
7763 * 주로 Sprite 애니메이션에 사용 된다.
7764 * <a href="../tutorial/timer_cycle.html" target="_blank">튜토리얼 보기</a>
7765 * 
7766 * timeline	------------------------------------&gt;
7767 * action	*-duration-* delay *-duration-* delay
7768 * 
7769 * @see collie.Timer
7770 * @class collie.AnimationCycle
7771 * @extends collie.Animation
7772 * @param {Function|collie.DisplayObject|Array} fCallback 실행될 콜백 함수, DisplayObject를 넣게 되면 해당 객체에 관한 내용만 변경함. htOption의 set 참조.
7773 * @param {collie.AnimationCycle} fCallback.timer 현재 타이머 인스턴스
7774 * @param {Number} fCallback.frame 현재 프레임
7775 * @param {Number} fCallback.duration 타이머에 설정된 duraiton 값
7776 * @param {Number} fCallback.count 실행 횟수
7777 * @param {Number} fCallback.skippedCount 지나간 실행 횟수
7778 * @param {Number} fCallback.runningTime 타이머 시작 후 실행된 시간 (ms)
7779 * @param {Variables} fCallback.value 싸이클 값
7780 * @param {Number} fCallback.cycle 싸이클 반복 횟수
7781 * @param {Number} fCallback.step 단계 값
7782 * @param {Number} fCallback.from 시작 값
7783 * @param {Number} fCallback.to 끝 값
7784 * @param {Number|String} nDuration 시간 간격 (ms), fps 단위를 사용할 수 있다.
7785 * @param {Number} htOption 설정
7786 * @param {Number} htOption.from=0 싸이클 시작 값
7787 * @param {Number} htOption.to=0 싸이클 끝 값
7788 * @param {Number} [htOption.step=1] 증감 값
7789 * @param {Number} [htOption.loop=0] 0이 아니면 해당 횟수만큼 반복
7790 * @param {Number} [htOption.useRealTime=true] SkippedFrame을 적용해서 싸이클을 현재 시간과 일치
7791 * @param {Array} [htOption.valueSet] 비 규칙적 cycle을 사용할 때 valueSet에 배열을 넣고 순서대로 값을 꺼내 쓸 수 있다
7792 * @param {String|Array} [htOption.set="spriteX"] fCallback에 DisplayObject를 넣을 경우 set을 이용해서 특정 값을 변경한ë
7792‹¤. 배열로 넣을 경우 여러 속성을 변경할 수 있다  
7793 * @param {Number} [htOption.start] from 값이 아닌 값부터 시작할 경우 값을 설정. ex) from:0, to:3 일 때 2, 3, 0, 1, 2, 3... 으로 진행할 경우 start:2 값을 설정
7794 * @example
7795 * valueSet 사용 방법, step, from, to 옵션은 자동으로 설정된다
7796 * <code>
7797 * collie.Timer.cycle(function () {
7798 * 	// 0, 1, 2, 1, 0 순으로 플레이
7799 * }, 1000, {
7800 * 	valueSet : [0, 1, 2, 1, 0]
7801 * });
7802 * </code>
7803 * 
7804 * DisplayObject를 callback으로 사용해서 스프라이트 애니메이션을 구현하는 방법
7805 * <code>
7806 * collie.Timer.cycle(oDisplayObject, 1000, {
7807 * 	valueSet : [0, 1, 2, 1, 0]
7808 * });
7809 * </code>
7810 * 
7811 * fps 단위를 쓰면 프레임 당 재생 속도를 설정할 수 있다. 8프레임이니 이 경우에 24fps는 (1000 / 24 * 8)ms가 된다.
7812 * <code>
7813 * collie.Timer.cycle(oDisplayObject, "24fps", {
7814 * 	from : 0,
7815 *  to : 7
7816 * });
7817 * </code>
7818 */
7819collie.AnimationCycle = collie.Class(/** @lends collie.AnimationCycle.prototype */{
7820	/**
7821	 * @constructs
7822	 */
7823	$init : function (fCallback, nDuration, htOption) {
7824		this._nFPS = null;
7825		this._htCallback = {};
7826		var fSetterFPS = this._setterFPS.bind(this);
7827		this.optionSetter("valueSet", this._setterValueSet.bind(this));
7828		this.optionSetter("to", fSetterFPS);
7829		this.optionSetter("from", fSetterFPS);
7830		this.option({
7831			delay : 0, // 다음 싸이클 까지의 대기 시간 ms
7832			from : 0, // 시작 값
7833			to : 0, // 끝 값
7834			step : 1, // 단계 값
7835			loop : 0, // 0이 아니면 반복횟수 제한
7836			set : "spriteX",
7837			useRealTime : true,
7838			valueSet : null,
7839			start : null // 시작값이 아닌 값부터 시작할 경우 지정
7840		});
7841		this.option(htOption || {});
7842		this._nFrameAtRunLastest = null;
7843		this._nRunLastestTime = null;
7844		this._nRunningTime = null;
7845		this._nCountCycle = 0;
7846		this._nCountCycleBefore = 0;
7847		this.setDuration(nDuration);
7848		this.reset();
7849	},
7850	
7851	/**
7852	 * 값을 초기화
7853	 */
7854	reset : function () {
7855		this._nCount = 0;
7856		this._nCountCycle = 0;
7857		this._nCountCycleBefore = 0;
7858		this._nFrameAtRunLastest = null;
7859		this._nRunningTime = null;
7860		this._nRunLastestTime = null;
7861		this._nValue = (this._htOption.start !== null ? this._htOption.start : this._htOption.from) - this._htOption.step;		
7862	},
7863	
7864	/**
7865	 * valueSet의 setter
7866	 * @private
7867	 */
7868	_setterValueSet : function () {
7869		var aValueSet = this._htOption.valueSet;
7870		
7871		// valueSet에 맞춰서 나머지 옵션을 변경 한다
7872		if (aValueSet && aValueSet instanceof Array) {
7873			this.option({
7874				from : 0,
7875				to : aValueSet.length - 1,
7876				step : 1
7877			});
7878		} 
7879	},
7880	
7881	/**
7882	 * @private
7883	 */
7884	_setterFPS : function () {
7885		if (this._nFPS !== null && typeof this._htOption.to !== "undefined" && typeof this._htOption.from !== "undefined") {
7886			var nCount = (this._htOption.to - this._htOption.from) + 1;
7887			this._nDuration = Math.round(1000 / this._nFPS * nCount); 
7888		}
7889	},
7890	
7891	/**
7892	 * fps 처리
7893	 * 
7894	 * @param {Number|String} nDuration 
7895	 * @private
7896	 */
7897	setDuration : function (nDuration) {
7898		this._nDuration = parseInt(nDuration, 10);
7899		
7900		if (/fps/i.test(nDuration) && typeof this._htOption.to !== "undefined" && typeof this._htOption.from !== "undefined") {
7901			this._nFPS = parseInt(nDuration, 10);
7902			this._setterFPS();
7903		} else {
7904			this._nFPS = null;
7905		}
7906	},
7907	
7908	/**
7909	 * 현재 값을 설정
7910	 * 
7911	 * @param {Variables} vValue
7912	 */
7913	setValue : function (vValue) {
7914		this._nValue = vValue;
7915	},
7916	
7917	/**
7918	 * 현재 값을 반환
7919	 * 
7920	 * @return {Variables}
7921	 */
7922	getValue : function () {
7923		return this._htOption.valueSet ? this._htOption.valueSet[this._nValue] : this._nValue;
7924	},
7925	
7926	/**
7927	 * 애니메이션을 실행
7928	 * 
7929	 * @param {Number} [nCurrentFrame] 현재 렌더러 프레임, 값이 없으면 자동으로 현재 렌더러 프레임을 가져 온다
7930	 * @param {Number} [nFrameDuration] 진행된 프레임 시간(ms)
7931	 */
7932	run : function (nCurrentFrame, nFrameDuration) {
7933		if (typeof nCurrentFrame === "undefined") {
7934			nCurrentFrame = collie.Renderer.getInfo().frame;
7935		}
7936		
7937		// stop 된 경우
7938		if (this._nFrameAtRunLastest > nCurrentFrame) {
7939			this.reset();
7940			return;
7941		}
7942		
7943		// 시작되지 않았을 때 시작 시점 기록
7944		if (this._nFrameAtRunLastest === null) {
7945			this._nFrameAtRunLastest = nCurrentFrame;
7946			this._nRunLastestTime = 0; // 마지막으로 실행됐던 시간
7947			this._nRunningTime = 0;
7948			nFrameDuration = 0; // 시작 시점에는 FrameDuration을 계산하지 않는다
7949		}
7950		
7951		if (!nFrameDuration) {
7952			nFrameDuration = 0;
7953		}
7954		
7955		var htOption = this._htOption;
7956		var nDiff = htOption.to - htOption.from;
7957		this._nTotalCount = nDiff / htOption.step; // 총 횟수
7958		this._nTerm = this._nDuration / this._nTotalCount; // 시간 간격
7959		this._nRunningTime += nFrameDuration; // 시작 시점부터 총 진행된 시간
7960		var nSkippedCount = (!htOption.useRealTime) ? 0 : Math.max(1, Math.floor((this._nRunningTime - this._nRunLastestTime) / this._nTerm)) - 1;
7961		
7962		// 실행되어야 할 시간이 지났다면 실행
7963		if (this._nRunningTime === 0 || this._nRunLastestTime + this._nTerm <= this._nRunningTime) {
7964			// 사이클이 끝나면 end 발생
7965			if (this._nCountCycleBefore !== this._nCountCycle) {
7966				/**
7967				 * 한 싸이클이 끝나면 발생함
7968				 * @name collie.AnimationCycle#end
7969				 * @event
7970				 * @param {Object} oEvent 컴포넌트 기본 이벤트 객체
7971				 */
7972				this.fireEvent("end");
7973			}
7974			
7975			// 반복 횟수를 넘었다면 종료
7976			if (htOption.loop && this._nCountCycle >= htOption.loop) {
7977				this.complete();
7978				return;
7979			}
7980			
7981			// 끝 값이면 시작 값으로 되돌림
7982			if (this._nValue === htOption.to) {
7983				this._nValue = htOption.from - htOption.step;
7984			}
7985			
7986			this._nValue += (htOption.step * (1 + nSkippedCount));
7987			this._nCount += (1 + nSkippedCount);
7988			this._nCountCycleBefore = this._nCountCycle;
7989			
7990			// 값을 벗어났을 때 처리
7991			if (htOption.from <= htOption.to ? this._nValue >= htOption.to : this._nValue <= htOption.to) {
7992				var nOverCount = (this._nValue - htOption.to) / htOption.step;
7993				var nOverCountCycle = Math.ceil(nOverCount / (this._nTotalCount + 1)); // 전체 숫자 카운트
7994				nOverCount = nOverCount % (this._nTotalCount + 1);
7995				
7996				if (nOverCount) { // 지나간 것
7997					this._nCountCycle += nOverCountCycle; 	
7998					this._nValue = htOption.from + (nOverCount - 1) * htOption.step;
7999				} else { // 정확히 끝난 것
8000					this._nCountCycle += 1;
8001					this._nValue = htOption.to;
8002				}
8003			}
8004			
8005			// 객체 재활용
8006			this._htCallback.timer = this;
8007			this._htCallback.frame = nCurrentFrame;
8008			this._htCallback.duration = this._nDuration;
8009			this._htCallback.count = this._nCount;
8010			this._htCallback.skippedCount = nSkippedCount;
8011			this._htCallback.runningTime = this._nRunningTime;
8012			this._htCallback.value = this.getValue();
8013			this._htCallback.cycle = this._nCountCycle;
8014			this._htCallback.step = htOption.step;
8015			this._htCallback.from = htOption.from;
8016			this._htCallback.to = htOption.to;
8017			this.triggerCallback(this._htCallback);
8018			
8019			// 시간 진행
8020			this._nFrameAtRunLastest = nCurrentFrame;
8021			this._nRunLastestTime = this._nRunningTime;
8022		}
8023	}
8024}, collie.Animation);
8025/**
8026 * 설정된 시간동안 지연 후에 실행되는 타이머
8027 * <a href="../tutorial/timer_delay.html" target="_blank">튜토리얼 보기</a>
8028 * 
8029 * timeline	----------&gt;
8030 * action	duration *
8031 * @see collie.Timer
8032 * @class collie.AnimationDelay
8033 * @extends collie.Animation
8034 * @param {Function} fCallback 실행될 콜백 함수
8035 * @param {collie.AnimationDelay} fCallback.timer 현재 타이머 인스턴스
8036 * @param {Number} fCallback.frame 현재 프레임
8037 * @param {Number} fCallback.duration 타이머에 설정된 duraiton 값
8038 * @param {Number} fCallback.runningTime 타이머 시작 후 실행된 시간 (ms)
8039 * @param {Number} nDuration 시간 간격 ms
8040 */
8041collie.AnimationDelay = collie.Class(/** @lends collie.AnimationDelay.prototype */{
8042	/**
8043	 * @constructs
8044	 */
8045	$init : function (fCallback, nDuration) {
8046		this.reset();
8047	},
8048
8049	/**
8050	 * 값을 초기화
8051	 */
8052	reset : function () {
8053		this._nFrameAtRunLastest = null;
8054		this._nRunningTime = null;
8055		this._nRunLastestTime = null;
8056	},
8057	
8058	/**
8059	 * 애니메이션을 실행
8060	 * 
8061	 * @param {Number} [nCurrentFrame] 현재 렌더러 프레임, 값이 없으면 자동으로 현재 렌더러 프레임을 가져 온다
8062	 * @param {Number} [nFrameDuration] 진행된 프레임 시간(ms)
8063	 */
8064	run : function (nCurrentFrame, nFrameDuration) {
8065		if (nCurrentFrame === undefined) {
8066			nCurrentFrame = collie.Renderer.getInfo().frame;
8067		}
8068		
8069		// stop 된 경우
8070		if (this._nFrameAtRunLastest > nCurrentFrame) {
8071			this.reset();
8072			return;
8073		}
8074		
8075		if (this._nFrameAtRunLastest === null) {
8076			this._nFrameAtRunLastest = nCurrentFrame;
8077			this._nRunLastestTime = 0;
8078			this._nRunningTime = 0;
8079			nFrameDuration = 0;
8080		}
8081		
8082		this._nRunningTime += nFrameDuration;
8083
8084		// 처음 실행되면 기록
8085		if (this._nRunLastestTime + this._nDuration <= this._nRunningTime) {
8086			if (this._fCallback) {
8087				this._fCallback({
8088					timer : this,
8089					frame : nCurrentFrame,
8090					duration : this._nDuration,
8091					runningTime : this._nRunningTime
8092				});
8093			}
8094			
8095			/**
8096			 * 애니메이션이 끝났을 때 발생
8097			 * @name collie.AnimationDelay#complete
8098			 * @event
8099			 */
8100			this.complete();
8101		}
8102	}
8103}, collie.Animation);
8104/**
8105 * 타임라인 기반으로 애니메이션을 실행시킴
8106 * <a href="../tutorial/timer_timeline.html" target="_blank">튜토리얼 보기</a>
8107 *  
8108 * @see collie.Timer
8109 * @class collie.AnimationTimeline
8110 * @extends collie.Animation
8111 * @param {Array} aTimeline 타임라인 배열
8112 * @param {Object} [htOption]
8113 * @param {Object} [htOption.loop=1] 반복 횟수, 0일 경우 무한 반복 한다
8114 * @example
8115 * // 생성과 동시에 타임라인을 정의
8116 * collie.Timer.timeline([
8117 * 		[0, "delay", function () {}, 1000],
8118 * 		[10, "transition", function () {}, 1000, { from:1, to:1 }],
8119 * ]);
8120 * 
8121 * @example
8122 * // 생성 후 타임라인을 정의
8123 * var timeline = collie.Timer.timeline();
8124 * timeline.add(0, "delay", function () {}, 1000);
8125 * timeline.add(10, "transition", function () {}, 1000, { from:1, to:1 });
8126 * 
8127 * @example
8128 * // 타임라인 액션을 삭제
8129 * var timeline = collie.Timer.timeline();
8130 * var action = timeline.add(0, "delay", function () {}, 1000);
8131 * timeline.remove(10); // 10ms에 실행되는 모든 액션을 삭제
8132 * timeline.remove(10, action); // action만 삭제 
8133 */
8134collie.AnimationTimeline = collie.Class(/** @lends collie.AnimationTimeline.prototype */{
8135	/**
8136	 * @constructs
8137	 */
8138	$init : function (aTimeline, htOption) {
8139		this.option("loop", 1);
8140		this.option(htOption || {});
8141		this.setOptionEvent(htOption);
8142		this._htAnimations = {};
8143		this._aTimeline = null;
8144		this._aRunningAnimation = null;
8145		this._nRunningTime = null;
8146		this._nCountCycle = 0;
8147		
8148		if (aTimeline) {
8149			for (var i = 0, l = aTimeline.length; i < l; i++) {
8150				this.addTimeline.apply(this, aTimeline[i]);
8151			}
8152		}
8153				
8154		this.reset();
8155	},
8156	
8157	/**
8158	 * 타임라인에 애니메이션을 추가
8159	 * 
8160	 * @param {Number} nStartTime 시작 시간(ms) 
8161	 * @param {String|collie.Animation} 애니메이션 이름이나 애니메이션 객체를 지정한다.
8162	 * @param {Function|Object} fCallback 각 애니메이션에 쓰이는 인자, queue 애니메이션일 경우 첫 번째 안자가 htOption이 된다
8163	 * @param {Number} nDuration 각 애니메이션에 쓰이는 인자
8164	 * @param {Object} htOption 각 애니메이션에 쓰이는 인자
8165	 * @return {collie.Animation} 만들어진 애니메이션
8166	 * @example
8167	 * var timeline = collie.Timer.timeline();
8168	 * 
8169	 * // queue를 사용하는 방법
8170	 * var queue = timeline.add(0, "queue");
8171	 * queue.cycle(item, 1000, { from:0, to:9 });
8172	 * 
8173	 * // 직접 Animation 객체를 생성
8174	 * timeline.add(100, new collie.AnimationCycle(item, 1000, { from:0, to:9 }));
8175	 */
8176	add : function (nStartTime, vType, fCallback, nDuration, htOption) {
8177		var oAnimation;
8178		
8179		// 애니메이션 인스턴스 생성
8180		switch (vType) {
8181			case "delay" :
8182				oAnimation = new collie.AnimationDelay(fCallback, nDuration, htOption);					
8183				break;
8184				
8185			case "repeat" :
8186				oAnimation = new collie.AnimationRepeat(fCallback, nDuration, htOption);
8187				break;
8188				
8189			case "transition" :
8190				oAnimation = new collie.AnimationTransition(fCallback, nDuration, htOption);
8191				break;
8192				
8193			case "cycle" :
8194				oAnimation = new collie.AnimationCycle(fCallback, nDuration, htOption);
8195				break;
8196				
8197			case "queue" :
8198				oAnimation = new collie.AnimationQueue(fCallback /* htOption임 */);
8199				break;
8200				
8201			default :
8202				if (vType instanceof collie.Animation) {
8203					oAnimation = vType;
8204				} else {
8205					throw new Error(vType + ' timer is not defined');
8206				}
8207		}
8208		
8209		this._addTimeline(nStartTime, oAnimation);
8210		return oAnimation;
8211	},
8212	
8213	/**
8214	 * 애니메이션 인스턴스를 추가
8215	 * 
8216	 * @private
8217	 * @param {Number} nStartTime 시작 시간(ms) 
8218	 * @param {collie.Animation} oAnimation 추가될 애니메이션
8219	 */
8220	_addTimeline : function (nStartTime, oAnimation) {
8221		nStartTime = parseInt(nStartTime, 10); // 정수로 변환
8222		this._htAnimations[nStartTime] = this._htAnimations[nStartTime] || []; 
8223		this._htAnimations[nStartTime].push(oAnimation);
8224		
8225		// 이미 초기화 됐다면 다시 초기화
8226		if (this._aTimeline !== null) {
8227			this.reset();
8228		}
8229	},
8230	
8231	/**
8232	 * 등록된 타임라인을 제거한다
8233	 * 
8234	 * @param {Number} nStartTime 시작 시간(ms)
8235	 * @param {collie.Animation} oTimer 지울 타이머, 값이 없으면 해당 시간대 전부를 지움
8236	 */
8237	remove : function (nStartTime, oTimer) {
8238		nStartTime = parseInt(nStartTime, 10); // 정수로 변환
8239		
8240		if (this._htAnimations && this._htAnimations[nStartTime]) {
8241			for (var i = 0; i < this._htAnimations[nStartTime].length; i++) {
8242				if (typeof oTimer === "undefined" || oTimer === this._htAnimations[nStartTime][i]) {
8243					this._htAnimations[nStartTime][i].stop();
8244					this._htAnimations[nStartTime].splice(i, 1);
8245					i--;
8246					
8247					if (typeof oTimer !== "undefined") {
8248						break;
8249					}
8250				}
8251			}
8252			
8253			// 지웠는데 더 이상 그 시간대에 타이머가 없을 경우 생성된 Timeline도 지움
8254			if (this._htAnimations[nStartTime].length < 1) {
8255				delete this._htAnimations[nStartTime];
8256				this._removeTimelineStartTime(nStartTime);
8257			}
8258		}
8259	},
8260	
8261	_removeTimelineStartTime : function (nStartTime) {
8262		if (this._aTimeline) {
8263			for (var i = 0, l = this._aTimeline.length; i < l; i++) {
8264				if (this._aTimeline[i] === nStartTime) {
8265					this._aTimeline.splice(i, 1);
8266					break;
8267				}
8268			}
8269		}
8270	},
8271	
8272	/**
8273	 * 타임라인을 초기화
8274	 * @private
8275	 */
8276	_initTimeline : function () {
8277		this._aTimeline = [];
8278		this._aRunningAnimation = [];
8279		
8280		// 시작 시간을 넣음
8281		for (var i in this._htAnimations) {
8282			this._aTimeline.push(parseInt(i, 10));
8283		} 
8284		
8285		// 정렬
8286		this._aTimeline.sort(function (a, b) {
8287			return a - b;
8288		});
8289	},
8290	
8291	/**
8292	 * 등록된 애니메이션 인스턴스를 반환한다
8293	 * 
8294	 * @param {Number} nStartTime 시작 시간(ms) 
8295	 * @return {Array|Boolean} 등록된 애니메이션이 없으면 false를 반환, 반환 형식은 항상 배열임
8296	 */
8297	getAnimation : function (nStartTime) {
8298		nStartTime = parseInt(nStartTime, 10); // 정수로 변환
8299		return (this._htAnimations && this._htAnimations[nStartTime]) ? this._htAnimations[nStartTime] : false;
8300	},
8301	
8302	/**
8303	 * 현재까지 진행된 시간을 반환
8304	 * @return {Number} ms 진행이 안된 상태면 0을 반환
8305	 */
8306	getRunningTime : function () {
8307		return this._nRunningTime || 0;
8308	},
8309	
8310	/**
8311	 * 현재까지 반복된 횟수
8312	 * @return {Number}
8313	 */
8314	getCycle : function () {
8315		return this._nCountCycle || 0;
8316	},
8317	
8318	/**
8319	 * 값을 초기화
8320	 */
8321	reset : function () {
8322		this._nFrameAtRunLastest = null;
8323		this._nRunningTime = null;		
8324		this._aTimeline = null;
8325		this._aRunningAnimation = null;
8326		this._nCountCycle = 0;
8327		this._initTimeline();
8328	},
8329	
8330	/**
8331	 * 애니메이션을 실행
8332	 * 
8333	 * @param {Number} [nCurrentFrame] 현재 렌더러 프레임, 값이 없으면 자동으로 현재 렌더러 프레임을 가져 온다
8334	 * @param {Number} [nFrameDuration] 진행된 프레임 시간(ms)
8335	 */
8336	run : function (nCurrentFrame, nFrameDuration) {
8337		if (nCurrentFrame === undefined) {
8338			nCurrentFrame = collie.Renderer.getInfo().frame;
8339		}
8340		
8341		// 렌더러가 stop 된 경우
8342		if (this._nFrameAtRunLastest > nCurrentFrame) {
8343			this.reset();
8344			return;
8345		}
8346		
8347		// 시작 프레임 저장
8348		if (this._nFrameAtRunLastest === null) {
8349			this._nFrameAtRunLastest = nCurrentFrame;
8350			this._nRunningTime = 0;
8351			nFrameDuration = 0;
8352		}
8353		
8354		this._nRunningTime += nFrameDuration;
8355		
8356		// 진행될 액션이 있을 경우 추가
8357		if (this._aTimeline.length > 0) {
8358			while (this._aTimeline[0] <= this._nRunningTime) {
8359				var nStartTime = this._aTimeline.shift();
8360				
8361				for (var i = 0, l = this._htAnimations[nStartTime].length; i < l; i++) {
8362					this._aRunningAnimation.push(this._htAnimations[nStartTime][i]);
8363					this._htAnimations[nStartTime][i].start();
8364				}
8365			}
8366		}
8367		
8368		// 진행중인 액션이 있을 경우 run 전달
8369		if (this._aRunningAnimation.length > 0) {
8370			for (var i = 0; i < this._aRunningAnimation.length; i++) {
8371				if (this._aRunningAnimation[i]) {
8372					this._aRunningAnimation[i].run(nCurrentFrame, nFrameDuration);
8373				}
8374				
8375				if (!this._aRunningAnimation[i] || !this._aRunningAnimation[i].isPlaying()) {
8376					if (this._aRunningAnimation[i]) {
8377						this._aRunningAnimation[i].reset();
8378					}
8379					
8380					this._aRunningAnimation.splice(i, 1);
8381					i--;
8382					this._checkComplete();
8383				}
8384			}
8385		}
8386	},
8387	
8388	_checkComplete : function () {
8389		// 끝났으면
8390		if (this._aRunningAnimation.length < 1 && this._aTimeline.length < 1) {
8391			this._nCountCycle++;
8392			
8393			if (this._htOption.loop && this._htOption.loop <= this._nCountCycle) {
8394				/**
8395				 * 계획된 모든 애니메이션과 반복 횟수가 끝나면 발생. loop=0으로 설정하면 발생하지 않는다.
8396				 * @name collie.AnimationTimeline#complete
8397				 * @event
8398				 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
8399				 */
8400				this.complete();
8401			} else {
8402				/**
8403				 * loop가 있을 경우 모든 타임라인 액션이 한 번 끝났을 때 발생
8404				 * @name collie.AnimationTimeline#end
8405				 * @event
8406				 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
8407				 */
8408				this.fireEvent("end");
8409				this._nFrameAtRunLastest = null;
8410				this._nRunningTime = null;		
8411				this._aTimeline = null;
8412				this._aRunningAnimation = null;
8413				this._initTimeline();
8414			}
8415		}
8416	}
8417}, collie.Animation);
8418/**
8419 * 계획된 여러 애니메이션을 다룰 수 있는 Queue
8420 * <a href="../tutorial/timer_queue.html" target="_blank">튜토리얼 보기</a>
8421 * 
8422 * @see collie.Timer
8423 * @class collie.AnimationQueue
8424 * @extends collie.Animation
8425 * @param {Object} [htOption]
8426 * @param {Object} [htOption.loop=1] 큐 반복 횟수, 0일 경우 무한 반복 한다
8427 * @example
8428 * collie.Timer.queue({ loop : 1 }).
8429 * 				delay(function () {}, 1000).
8430 *				transition(function () {}, 1000, { from : 1, to : 1 });
8431 */
8432collie.AnimationQueue = collie.Class(/** @lends collie.AnimationQueue.prototype */{
8433	/**
8434	 * @constructs
8435	 */
8436	$init : function (htOption) {
8437		this.option("loop", 1);
8438		this.option(htOption || {});
8439		this.setOptionEvent(htOption);
8440		this._aAnimations = [];
8441		this._fOnCompleteAnimation = this._onCompleteAnimation.bind(this);
8442		this.reset();
8443	},
8444	
8445	/**
8446	 * queue에 delay 애니메이션을 추가한다
8447	 * @see collie.AnimationDelay
8448	 * @return {collie.AnimationQueue} 메서드 체이닝 사용 가능
8449	 */
8450	delay : function (fCallback, nDuration, htOption) {
8451		this._add(new collie.AnimationDelay(fCallback, nDuration, htOption));
8452		return this;
8453	},
8454	
8455	/**
8456	 * queue에 repeat 애니메이션을 추가한다
8457	 * @see collie.AnimationRepeat
8458	 * @return {collie.AnimationQueue} 메서드 체이닝 사용 가능
8459	 */
8460	repeat : function (fCallback, nDuration, htOption) {
8461		this._add(new collie.AnimationRepeat(fCallback, nDuration, htOption));
8462		return this;
8463	},
8464	
8465	/**
8466	 * queue에 transition 애니메이션을 추가한다
8467	 * @see collie.AnimationTransition
8468	 * @return {collie.AnimationQueue} 메서드 체이닝 사용 가능
8469	 */
8470	transition : function (fCallback, nDuration, htOption) {
8471		this._add(new collie.AnimationTransition(fCallback, nDuration, htOption));
8472		return this;
8473	},
8474	
8475	/**
8476	 * queue에 cycle 애니메이션을 추가한다
8477	 * @see collie.AnimationCycle
8478	 * @return {collie.AnimationQueue} 메서드 체이닝 사용 가능
8479	 */
8480	cycle : function (fCallback, nDuration, htOption) {
8481		this._add(new collie.AnimationCycle(fCallback, nDuration, htOption));
8482		return this;
8483	},
8484	
8485	/**
8486	 * 등록된 애니메이션 인스턴스를 반환한다
8487	 * 
8488	 * @param {Number} nIdx 등록 순서 (0~)
8489	 * @return {collie.Animation}
8490	 */
8491	getAnimation : function (nIdx) {
8492		return this._aAnimations[nIdx] || false;
8493	},
8494	
8495	/**
8496	 * 애니메이션 인스턴스를 추가
8497	 * 
8498	 * @private
8499	 * @param {collie.Animation} oAnimation 추가될 애니메이션
8500	 */
8501	_add : function (oAnimation) {
8502		oAnimation.attach("complete", this._fOnCompleteAnimation);
8503		this._aAnimations.push(oAnimation);
8504	},
8505	
8506	/**
8507	 * 각 애니메이션이 종료되었을 때 처리하는 이벤트 핸들러
8508	 * @private
8509	 */
8510	_onCompleteAnimation : function () {
8511		this.next();
8512	},
8513
8514	/**
8515	 * 다음 애니메이션으로 넘긴다
8516	 */
8517	next : function () {
8518		if (this._nAnimationIdx === null) {
8519			this._nAnimationIdx = 0;
8520		} else {
8521			this._nAnimationIdx++;
8522		}
8523		
8524		// 종료되면
8525		if (this._nAnimationIdx >= this._aAnimations.length) {
8526			this._nCount++;
8527			
8528			/**
8529			 * 계획된 모든 애니메이션이 끝날 때 마다 발생, loop 설정과 관계 없이 매번 일어난다
8530			 * @name collie.AnimationQueue#end
8531			 * @event
8532			 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
8533			 * @param {Object} oEvent.count 현재까지 반복된 횟수
8534			 */
8535			this.fireEvent("end", {
8536				count : this._nCount
8537			});
8538			
8539			// loop 설정이 있으면 되돌림
8540			if (!this._htOption.loop || this._htOption.loop > this._nCount) {
8541				this._nAnimationIdx = 0;
8542			} else {
8543				/**
8544				 * 계획된 모든 애니메이션과 반복 횟수가 끝나면 발생. loop=0으로 설정하면 발생하지 않는다.
8545				 * @name collie.AnimationQueue#complete
8546				 * @event
8547				 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
8548				 */
8549				this.complete();
8550				return;
8551			}
8552		}
8553		
8554		this._aAnimations[this._nAnimationIdx].stop();
8555		this._aAnimations[this._nAnimationIdx].start();
8556	},
8557	
8558	/**
8559	 * 값을 초기화
8560	 */
8561	reset : function () {
8562		this._nFrameAtRunLastest = null;
8563		this._nAnimationIdx = null;
8564		this._nCount = 0;
8565	},
8566	
8567	/**
8568	 * 등록된 모든 애니메이션을 제거
8569	 */
8570	removeAll : function () {
8571		this._aAnimations = [];
8572		this.reset();
8573	},
8574	
8575	/**
8576	 * 현재 진행 중인 애니메이션까지 남기고 나머지를 지움
8577	 */
8578	removeAfter : function () {
8579		if (this._nAnimationIdx + 1 <= this._aAnimations.length - 1) {
8580			var count = this._aAnimations.length - (this._nAnimationIdx + 1); 
8581			this._aAnimations.splice(this._nAnimationIdx + 1, count);
8582		}
8583	},
8584	
8585	/**
8586	 * 애니메이션을 실행
8587	 * 
8588	 * @param {Number} [nCurrentFrame] 현재 렌더러 프레임, 값이 없으면 자동으로 현재 렌더러 프레임을 가져 온다
8589	 * @param {Number} [nFrameDuration] 진행된 프레임 시간(ms)
8590	 */
8591	run : function (nCurrentFrame, nFrameDuration) {
8592		// 등록된 애니메이션이 없는 경우에는 지나감
8593		if (this._aAnimations.length < 1) {
8594			return; 
8595		}
8596		
8597		if (nCurrentFrame === undefined) {
8598			nCurrentFrame = collie.Renderer.getInfo().frame;
8599		}
8600		
8601		// 렌더러가 stop 된 경우
8602		if (this._nFrameAtRunLastest > nCurrentFrame) {
8603			this.reset();
8604			return;
8605		}
8606		
8607		// 시작되지 않았을 때 시작 시점 기록
8608		if (this._nFrameAtRunLastest === null) {
8609			this._nFrameAtRunLastest = nCurrentFrame;
8610		}
8611		
8612		if (this._nAnimationIdx === null) {
8613			this.next();
8614		}
8615		
8616		this._aAnimations[this._nAnimationIdx].run(nCurrentFrame, nFrameDuration);
8617	}
8618}, collie.Animation);
8619/**
8620 * Timer 목록
8621 * 
8622 * @private
8623 * @class collie.TimerList
8624 */
8625collie.TimerList = collie.Class(/** @lends collie.TimerList.prototype */{
8626	/**
8627	 * @constructs
8628	 * @private
8629	 */
8630	$init : function () {
8631		this._aList = [];
8632	},
8633	
8634	/**
8635	 * 애니메이션 추가
8636	 * 
8637	 * @param {collie.Animation} oAnimation
8638	 */
8639	add : function (oAnimation) {
8640		this._aList.unshift(oAnimation); // for문을 거꾸로 돌리기 위해 앞에서부터 삽입
8641	},
8642	
8643	/**
8644	 * 애니메이션 제거(멈춤이라고 보면 됨)
8645	 * 
8646	 * @param {collie.Animation} oAnimation 제거할 애니메이션 인스턴스 
8647	 */
8648	remove : function (oAnimation) {
8649		for (var i = 0, len = this._aList.length; i < len; i++) {
8650			if (this._aList[i] === oAnimation) {
8651				this._aList.splice(i, 1);
8652				break;
8653			}
8654		}
8655	},
8656	
8657	/**
8658	 * 애니메이션을 모두 제거
8659	 */
8660	removeAll : function () {
8661		this._aList = [];
8662	},
8663	
8664	/**
8665	 * 애니메이션을 모두 멈춤
8666	 */
8667	stopAll : function () {
8668		for (var i = 0, len = this._aList.length; i < len; i++) {
8669			this._aList[i].stop();
8670		}
8671	},
8672	
8673	/**
8674	 * 애니메이션을 실행
8675	 * 
8676	 * @param {Number} nCurrentFrame 현재 프레임을 Animation 인스턴스에 전달함
8677	 * @param {Number} nFrameDuration 진행된 프레임 시간(ms)
8678	 */
8679	run : function (nCurrentFrame, nFrameDuration) {
8680		// 뒤에서 부터 실행해서 중간에 삭제되도 for문이 동작하도록 함
8681		for (var i = this._aList.length - 1; i >= 0; i--) {
8682			if (this._aList[i]) {
8683				if (this._aList[i].isPlaying()) {
8684					this._aList[i].run(nCurrentFrame, nFrameDuration);
8685				} else {
8686					// 애니메이션이 실행 중이 아닌데도 리스트에 있다면 제거
8687					this._aList.splice(i, 1);
8688				}
8689			} 
8690		}
8691	}
8692});
8693/**
8694 * 타이머를 생성 / 관리. 모든 타이머는 collie.Timer에서 생성한다
8695 * @namespace
8696 */
8697collie.Timer = collie.Timer || new (collie.Class(/** @lends collie.Timer */{
8698	$init : function () {
8699		this._oList = new collie.TimerList();
8700	},
8701	
8702	/**
8703	 * 렌더러에서 렌더링 하기 전에 타이머를 실행 한다.
8704	 * 
8705	 * @param {Number} nCurrentFrame 현재 프레임
8706	 * @param {Number} nFrameDuration 진행된 프레임 시간(ms)
8707	 */
8708	run : function (nCurrentFrame, nFrameDuration) {
8709		this._oList.run(nCurrentFrame, nFrameDuration);
8710	},
8711	
8712	/**
8713	 * 전체를 멈춘다
8714	 * - 개별적으로 멈추는건 각각 타이머 인스턴스에서 stop을 호출
8715	 */
8716	stopAll : function () {
8717		this._oList.stopAll();
8718	},
8719	
8720	/**
8721	 * 전체 타이머를 제거 한다
8722	 */
8723	removeAll : function () {
8724		this._oList.removeAll();
8725	},
8726	
8727	/**
8728	 * @see collie.AnimationQueue
8729	 * @arguments collie.AnimationQueue
8730	 * @return {collie.AnimationQueue}
8731	 */
8732	queue : function (htOption) {
8733		var oAnimation = new collie.AnimationQueue(htOption);
8734		oAnimation.setTimerList(this._oList);
8735		return oAnimation;
8736	},
8737	
8738	/**
8739	 * @see collie.AnimationRepeat
8740	 * @arguments collie.AnimationRepeat
8741	 * @return {collie.AnimationRepeat}
8742	 */
8743	repeat : function (fCallback, nDuration, htOption) {
8744		var oAnimation = new collie.AnimationRepeat(fCallback, nDuration, htOption);
8745		oAnimation.setTimerList(this._oList);
8746		return oAnimation;
8747	},
8748	
8749	/**
8750	 * @see collie.AnimationTransition
8751	 * @arguments collie.AnimationTransition
8752	 * @return {collie.AnimationTransition}
8753	 */
8754	transition : function (fCallback, nDuration, htOption) {
8755		var oAnimation = new collie.AnimationTransition(fCallback, nDuration, htOption);
8756		oAnimation.setTimerList(this._oList);
8757		return oAnimation;
8758	},
8759	
8760	/**
8761	 * @see collie.AnimationCycle
8762	 * @arguments collie.AnimationCycle
8763	 * @return {collie.AnimationCycle}
8764	 */
8765	cycle : function (fCallback, nDuration, htOption) {
8766		var oAnimation = new collie.AnimationCycle(fCallback, nDuration, htOption);
8767		oAnimation.setTimerList(this._oList);
8768		return oAnimation;
8769	},
8770	
8771	/**
8772	 * @see collie.AnimationDelay
8773	 * @arguments collie.AnimationDelay
8774	 * @return {collie.AnimationDelay}
8775	 */
8776	delay : function (fCallback, nDuration, htOption) {
8777		var oAnimation = new collie.AnimationDelay(fCallback, nDuration, htOption);
8778		oAnimation.setTimerList(this._oList);
8779		return oAnimation;
8780	},
8781	
8782	/**
8783	 * @see collie.AnimationTimeline
8784	 * @arguments collie.AnimationTimeline
8785	 * @return {collie.AnimationTimeline}
8786	 */
8787	timeline : function (aTimeline, htOption) {
8788		var oAnimation = new collie.AnimationTimeline(aTimeline, htOption);
8789		oAnimation.setTimerList(this._oList);
8790		return oAnimation;
8791	}
8792}))();
8793/**
8794 * Layer를 등록해서 그리는 렌더링 파이프라인
8795 * @namespace
8796 * @TODO frame을 기록하는 클래스의 경우 stop되고 다시 시작되면 0부터 시작하므로
8797자기가 기록한 frame이 현재 frame보다 클 때 보정 처리를 반드시 해줘야 한다.
8798이는 나중에 frame이 int 풀카운트가 되었을 때 처리가 있을지도 모르므로 필수
8799 */
8800collie.Renderer = collie.Renderer || new (collie.Class(/** @lends collie.Renderer */{
8801	/**
8802	 * 기본 렌더링 FPS
8803	 * @type {String}
8804	 */
8805	DEFAULT_FPS : "60fps",
8806	
8807	/**
8808	 * 레티나 디스플레이 여부 auto 값일 경우 자동 판단, true/false 값은 수동
8809	 * @type {String|Boolean} auto 일 때 자동 판단 됨
8810	 */
8811	RETINA_DISPLAY : false,
8812	
8813	/**
8814	 * 이 값을 true로 변경하면 가상 딜레이를 발생할 수 있다.
8815	 * 가상 딜레이 발생 상태에서는 requestAnimationFrame이 동작하지 않으며
8816	 * 타이머 등이 스킵될 때 어떻게 동작하는지 확인할 수 있다.
8817	 *
8818	 * @type {Boolean}
8819	 * @example
8820	 * collie.Renderer.DEBUG_USE_DELAY = true;
8821	 * collie.Renderer.DEBUG_MAX_DELAY = 200;
8822	 * collie.Renderer.start();
8823	 */
8824	DEBUG_USE_DELAY : false,
8825	
8826	/**
8827	 * 가상 딜레이 최대값(랜덤하게 발생, ms)
8828	 * @type {Number}
8829	 */
8830	DEBUG_MAX_DELAY : 200,
8831	
8832	/**
8833	 * 렌더링 모드 [auto|canvas|dom]
8834	 * @type {String}
8835	 */
8836	DEBUG_RENDERING_MODE : "auto",
8837	
8838	$init : function () {
8839		this._sVisibilityChange = this._getNamePageVisibility();
8840		this._bPlaying = false;
8841		this._bPause = false;
8842		this._nFPS = 0;
8843		this._nDuration = 0; // ms
8844		this._nCurrentFrame = 0;
8845		this._nSkippedFrame = 0;
8846		this._nBeforeFrameTime = null; // ms
8847		this._nBeforeRenderingTime = 0; // ms
8848		this._aLayerList = [];
8849		this._fRender = this._render.bind(this);
8850		this._fCallback = null;
8851		this._htCallback = {};
8852		this._elContainer = document.createElement("div");
8853		this._elContainer.className = "_collie_container";
8854		this._elContainer.style.position = "relative";
8855		this._elContainer.style.overflow = "hidden";
8856		this._elParent = null;
8857		this._nDebugDelayedTime = 0;
8858		this._oRenderingTimer = null;
8859		this._bLoaded = false;
8860		this._sRenderingMode = null;
8861		this._bUseRetinaDisplay = null;
8862		this._htEventStatus = {};
8863		this._htPosition = {};
8864		this._bIsPreventDefault = true;
8865		this._htDeviceInfo = collie.util.getDeviceInfo();
8866		
8867		// PageVisibility API를 사용할 수 있다면 사용
8868		if (this._sVisibilityChange) {
8869			collie.util.addEventListener(document, this._sVisibilityChange, this._onChangeVisibility.bind(this));
8870		// 모바일이라면 pageshow/pagehide를 사용
8871		// In-App Browser일 때 pageshow/pagehide가 정상적으로 호출 안되는 문제점이 있음
8872		} else if (!this._htDeviceInfo.desktop) {
8873			collie.util.addEventListener(window, "pageshow", this._onPageShow.bind(this));
8874			collie.util.addEventListener(window, "pagehide", this._onPageHide.bind(this));
8875		}
8876		
8877		// 렌더러 엘리먼트의 위치를 저장해 놓는다
8878		collie.util.addEventListener(window, "resize", this.refresh.bind(this));
8879	},
8880	
8881	/**
8882	 * 페이지를 진입할 때 렌더러 처리
8883	 * @private
8884	 */
8885	_onPageShow : function () {
8886		if (!this.isPlaying() && this._bPause) {
8887			this.resume();
8888		}
8889	},
8890	
8891	/**
8892	 * 페이지를 이탈할 때 렌더러 처리
8893	 * @private
8894	 */
8895	_onPageHide : function () {
8896		if (this.isPlaying()) {
8897			this.pause();
8898		}
8899	},
8900	
8901	/**
8902	 * @private
8903	 */
8904	_onChangeVisibility : function () {
8905		var state = document.visibilityState || document.webkitVisibilityState || document.mozVisibilityState;
8906
8907		if (state === "hidden") {
8908			this.pause();
8909		} else if (state === "visible") {
8910			this.resume();
8911		}
8912	},
8913	
8914	/**
8915	 * 렌더링 엘리먼트의 위치를 갱신한다
8916	 * 만일 렌더링 엘리먼트의 위치가 load 후에 변경될 경우 refresh 메소드를 실행시켜줘야 한다
8917	 */
8918	refresh : function () {
8919		if (this._elParent !== null) {
8920			this._htPosition = collie.util.getPosition(this._elParent);
8921		}
8922	},
8923	
8924	/**
8925	 * 렌더러 엘리먼트의 현재 위치를 반환
8926	 * 렌더러가 load되지 않았다면 false를 반환
8927	 * 
8928	 * @private
8929	 * @return {Object|Boolean} htResult
8930	 * @return {Number} htResult.x 페이지 처음부터의 x좌표
8931	 * @return {Number} htResult.y 페이지 처음부터의 y좌표 
8932	 * @return {Number} htResult.width 너비
8933	 * @return {Number} htResult.height 높이
8934	 */
8935	getPosition : function () {
8936		return this._bLoaded ? this._htPosition : false;
8937	},
8938	
8939	/**
8940	 * 렌더러에 적용할 레이어를 추가 한다
8941	 * 
8942	 * @param {collie.Layer} oLayer
8943	 */
8944	addLayer : function (oLayer) {
8945		if (!oLayer || !("type" in oLayer) || oLayer.type !== "layer") {
8946			throw new Error('oLayer is not Layer instnace');
8947		}
8948		
8949		// 이미 추가된 레이어라면 무시
8950		for (var i = 0, len = this._aLayerList.length; i < len; i++) {
8951			if (this._aLayerList[i] === oLayer) {
8952				return;
8953			}
8954		}
8955		
8956		this._aLayerList.push(oLayer);
8957		
8958		// 로드된 상태에서는 자동으로 붙기
8959		if (this._bLoaded) {
8960			oLayer.load(this._elContainer, this._aLayerList.length);
8961			this.resetLayerEvent();
8962		}
8963	},
8964	
8965	/**
8966	 * 렌더러에 적용한 레이어를 제거 한다
8967	 * 
8968	 * @param {collie.Layer} oLayer
8969	 */
8970	removeLayer : function (oLayer) {
8971		for (var i = 0, len = this._aLayerList.length; i < len; i++) {
8972			if (this._aLayerList[i] === oLayer) {
8973				this._aLayerList[i].unload(); // 로딩되어 있으면 해제 시킴
8974				this._aLayerList.splice(i, 1);
8975				return;
8976			}
8977		}
8978	},
8979	
8980	/**
8981	 * 등록된 모든 레이어를 제거 한다
8982	 */
8983	removeAllLayer : function () {
8984		for (var i = this._aLayerList.length - 1; i >= 0; i--) {
8985			this._aLayerList[i].unload();
8986		}
8987		
8988		this._aLayerList = [];
8989	},
8990	
8991	/**
8992	 * 등록된 레이어를 모두 반환
8993	 * 
8994	 * @return {Array}
8995	 */
8996	getLayers : function () {
8997		return this._aLayerList;
8998	},
8999	
9000	/**
9001	 * 이벤트를 모두 해제하고 다시 건다
9002	 * @private
9003	 */
9004	resetLayerEvent : function () {
9005		for (var i = 0, len = this._aLayerList.length; i < len; i++) {
9006			this._aLayerList[i].detachEvent();
9007		}
9008
9009		// 레이어 역순으로 이벤트가 동작해야 하기 때문에 이벤트는 역순으로 건다
9010		for (var i = this._aLayerList.length - 1; i >= 0; i--) {
9011			this._aLayerList[i].attachEvent();
9012		}
9013	},
9014	
9015	/**
9016	 * 렌더러의 컨테이너 엘리먼트를 반환
9017	 * @return {HTMLElement}
9018	 */
9019	getElement : function () {
9020		return this._elContainer;
9021	},
9022	
9023	/**
9024	 * 렌더러에 적용된 시간을 반환
9025	 * 
9026	 * @return {Number} ms
9027	 */
9028	getDuration : function () {
9029		return this._nDuration;
9030	},
9031	
9032	/**
9033	 * 렌더러 정보를 반환
9034	 * 
9035	 * @return {Object} htInfo
9036	 * @return {Number} htInfo.frame 현재 프레임 수
9037	 * @return {Number} htInfo.skippedFrame 지나간 누적 프레임 수
9038	 * @return {Number} htInfo.fps
9039	 * @return {Number} htInfo.duration 지연시간(ms)
9040	 * @return {Number} htInfo.renderingTime 이전에 발생했던 렌더링 시간(ms)
9041	 * @return {Number} htInfo.beforeFrameTime 이전에 렌더러가 실행됐던 시간(timestamp)
9042	 */
9043	getInfo : function () {
9044		// 객체 재활용
9045		this._htCallback.frame = this._nCurrentFrame;
9046		this._htCallback.skippedFrame = this._nSkippedFrame;
9047		this._htCallback.fps = this._nFPS;
9048		this._htCallback.duration = this._nDuration;
9049		this._htCallback.renderingTime = this._nBeforeRenderingTime;
9050		this._htCallback.beforeFrameTime = this._nBeforeFrameTime;
9051		return this._htCallback;
9052	},
9053	
9054	/**
9055	 * 렌더링 모드를 반환
9056	 * - 두개의 방식을 섞어 쓰는 것은 속도가 느려서 1가지 방식을 사용하는 것이 낫다
9057	 * @return {String} [dom|canvas]
9058	 */
9059	getRenderingMode : function () {
9060		if (this._sRenderingMode === null) {
9061			var htDeviceInfo = collie.util.getDeviceInfo();
9062			this._sRenderingMode = this.DEBUG_RENDERING_MODE;
9063
9064			if (!this._sRenderingMode || this._sRenderingMode === "auto") {
9065				// 안드로이드 2.2 미만, 캔버스를 지원하지 않거나 ios 5 미만인 경우
9066				// 안드로이드 4 버전대에서 DOM 불안정성이 발견됨
9067				// 안드로이드 4.2.2 galaxy S4 부터 canvas가 더 빨라짐			
9068				if (
9069					(
9070						htDeviceInfo.android && !htDeviceInfo.chrome && (
9071							(htDeviceInfo.android < 4.2 && htDeviceInfo.android >= 3) ||
9072							htDeviceInfo.android < 2.2
9073						)
9074					) ||
9075					!htDeviceInfo.supportCanvas ||
9076					(htDeviceInfo.ios && htDeviceInfo.ios < 5)
9077				) {
9078					this._sRenderingMode = "dom";
9079				} else {
9080					this._sRenderingMode = "canvas";
9081				}
9082			}
9083			
9084			// 캔버스를 지원하지 않으면 무조건 DOM 모드로
9085			if (!htDeviceInfo.supportCanvas) {
9086				this._sRenderingMode = "dom";
9087			}
9088		}
9089		
9090		return this._sRenderingMode;
9091	},
9092	
9093	/**
9094	 * 렌더링 모드를 변경 한다
9095	 * 
9096	 * @param {String} sMode [auto|dom|canvas]
9097	 * @example collie를 사용하기 전에 사용해야 한다.
9098	 * collie.Renderer.setRenderingMode("dom");
9099	 * collie.ImageManager.add({
9100	 * 	...
9101	 * }, function () {
9102	 * 	...
9103	 * });
9104	 */
9105	setRenderingMode : function (sMode) {
9106		this.DEBUG_RENDERING_MODE = sMode.toString().toLowerCase();
9107		this._sRenderingMode = null;
9108	},
9109	
9110	/**
9111	 * 레티나 디스플레이를 사용하고 있는지 여부
9112	 * IE9 미만에서는 무조건 false를 반환
9113	 * 
9114	 * @return {Boolean}
9115	 */
9116	isRetinaDisplay : function () {
9117		if (this._bUseRetinaDisplay === null) {
9118			// android 4.0 이상도 retina 지원 추가
9119			this._bUseRetinaDisplay = this.RETINA_DISPLAY !== "auto" ? this.RETINA_DISPLAY : window.devicePixelRatio >= 2 && (!collie.util.getDeviceInfo().android || collie.util.getDeviceInfo().android >= 4);
9120			var htDeviceInfo = collie.util.getDeviceInfo();
9121			
9122			// background-size를 지원하지 않는 상태에서 고해상도 디스플레이 모드 사용할 수 없음
9123			if (htDeviceInfo.ie && htDeviceInfo.ie < 9) {
9124				this._bUseRetinaDisplay = false;
9125			}
9126		}
9127		
9128		return this._bUseRetinaDisplay;
9129	},
9130	
9131	/**
9132	 * 레티나 디스플레이 방식을 변경 한다
9133	 * @param {Boolean|String} vMode [false|true|"auto"]
9134	 * @example collie를 사용하기 전에 사용해야 한다.
9135	 * collie.Renderer.setRetinaDisplay(true);
9136	 * collie.ImageManager.add({
9137	 * 	...
9138	 * }, function () {
9139	 * 	...
9140	 * });
9141	 */
9142	setRetinaDisplay : function (vMode) {
9143		this.RETINA_DISPLAY = vMode;
9144		this._bUseRetinaDisplay = null;
9145	},
9146	
9147	/**
9148	 * requestAnimationFrame 사용 여부 반환
9149	 * 
9150	 * @private
9151	 * @param {Boolean} bCancelName true면 CancelAnimationFrame 이름을 반환
9152	 * @return {bool|String} 사용 가능하면 함수명을 반환
9153	 */
9154	_getNameAnimationFrame : function (bCancelName) {
9155		if (typeof window.requestAnimationFrame !== "undefined") {
9156			return bCancelName ? "cancelAnimationFrame" : "requestAnimationFrame";
9157		} else if (typeof window.webkitRequestAnimationFrame !== "undefined") {
9158			return bCancelName ? "webkitCancelAnimationFrame" : "webkitRequestAnimationFrame";
9159		} else if (typeof window.msRequestAnimationFrame !== "undefined") {
9160			return bCancelName ? "msCancelAnimationFrame" : "msRequestAnimationFrame";
9161		} else if (typeof window.mozRequestAnimationFrame !== "undefined") {
9162			return bCancelName ? "mozCancelAnimationFrame" : "mozRequestAnimationFrame";
9163		} else if (typeof window.oRequestAnimationFrame !== "undefined") {
9164			return bCancelName ? "oCancelAnimationFrame" : "oRequestAnimationFrame";
9165		} else {
9166			return false;
9167		}
9168	},
9169	
9170	/**
9171	 * Page Visibility Event 이름을 반환
9172	 * @private
9173	 * @return {String|Boolean}
9174	 */
9175	_getNamePageVisibility : function () {
9176		if ("hidden" in document) {
9177			return "visibilitychange";
9178		} else if ("webkitHidden" in document) {
9179			return "webkitvisibilitychange";
9180		} else if ("mozHidden" in document) {
9181			return "mozvisibilitychange";
9182		} else {
9183			return false;
9184		} 
9185	},
9186	
9187	/**
9188	 * 표현할 레이어를 elParent에 붙인다 시작전에 반드시 해야함
9189	 * 
9190	 * @param {HTMLElement} elParent
9191	 */
9192	load : function (elParent) {
9193		this.unload();
9194		this._bLoaded = true;
9195		this._elParent = elParent;
9196		this._elParent.appendChild(this._elContainer);
9197		this.refresh();
9198		
9199		if (this._aLayerList.length) {
9200			for (var i = 0, len = this._aLayerList.length; i < len; i++) {
9201				this._aLayerList[i].load(this._elContainer, i);
9202			}
9203			
9204			// 레이어 역순으로 이벤트가 동작해야 하기 때문에 이벤트는 역순으로 건다
9205			for (var i = this._aLayerList.length - 1; i >= 0; i--) {
9206				this._aLayerList[i].attachEvent();
9207			}
9208		}
9209		
9210	},
9211	
9212	/**
9213	 * 부모 엘리먼트에 붙인 레이어를 지움
9214	 */
9215	unload : function () {
9216		if (this._bLoaded) {
9217			for (var i = 0, len = this._aLayerList.length; i < len; i++) {
9218				this._aLayerList[i].unload();
9219			}
9220	
9221			this._elParent.removeChild(this._elContainer);
9222			this._elParent = null;
9223			this._bLoaded = false;
9224		}
9225	},
9226	
9227	/**
9228	 * 렌더링 시작
9229	 * - callback 안에서 false를 반환하면 rendering을 멈춘다
9230	 * 
9231	 * @param {Number|String} vDuration 렌더러의 시간 간격(ms), fps를 붙이면 fps 단위로 입력된다.
9232	 * @param {Function} fCallback 프레임마다 실행할 함수, 없어도 되고 process 이벤트를 받아서 처리해도 된다.
9233	 * @param {Number} fCallback.frame 현재 프레임
9234	 * @param {Number} fCallback.skippedFrame 시간이 밀려서 지나간 프레임 수
9235	 * @param {Number} fCallback.fps FPS
9236	 * @param {Number} fCallback.duration 지연 시간 (ms)
9237	 * @example
9238	 * fps를 붙이면 FPS단위로 입력할 수 있다.
9239	 * <code>
9240	 * collie.Renderer.start("30fps");
9241	 * collie.Renderer.start(1000 / 30);
9242	 * </code>
9243	 */
9244	start : function (vDuration, fCallback) {
9245		if (!this._bPlaying) {
9246			// this.stop();
9247			vDuration = vDuration || this.DEFAULT_FPS;
9248			this._nDuration = (/fps$/i.test(vDuration)) ? 1000 / parseInt(vDuration, 10) : Math.max(16, vDuration);
9249			this._fCallback = fCallback || null;
9250			this._bPlaying = true;
9251			
9252			// FPS가 60일 때만 requestAnimationFrame을 사용한다
9253			if (this._nDuration < 17) {
9254				this._sRequestAnimationFrameName = this._getNameAnimationFrame();
9255				this._sCancelAnimationFrameName = this._getNameAnimationFrame(true);
9256			} else {
9257				this._sRequestAnimationFrameName = false;
9258				this._sCancelAnimationFrameName = false;
9259			}
9260			
9261			/**
9262			 * 렌더링 시작
9263			 * @name collie.Renderer#start
9264			 * @event
9265			 * @param {Object} oEvent
9266			 */
9267			this.fireEvent("start");
9268			this._trigger(0);
9269		}
9270		
9271	},
9272	
9273	_trigger : function (nDelay) {
9274		if (!this._sVisibilityChange) {
9275			if (window.screenTop < -30000) {
9276				this.pause();
9277			}
9278		}
9279		
9280		if (typeof nDelay === "undefined") {
9281			nDelay = 0;
9282		} else {
9283			nDelay = parseInt(nDelay, 10);
9284		}
9285		
9286		// 가상 딜레이를 적용하려면 requestAnimationFrame을 제거
9287		if (this._sRequestAnimationFrameName !== false && !this.DEBUG_USE_DELAY) {
9288			this._oRenderingTimer = window[this._sRequestAnimationFrameName](this._fRender);
9289		} else {
9290			this._oRenderingTimer = setTimeout(this._fRender, nDelay);
9291		}
9292	},
9293	
9294	/**
9295	 * 실제 화면을 렌더링
9296	 * 
9297	 * @private
9298	 * @param {Number} nSkippedFrame collie.Renderer#draw 에서 넘어온 인자
9299	 * @param {Boolean} 실행중 여부와 관계 없이 그림
9300	 */
9301	_render : function (nSkippedFrame, bForcePlay) {
9302		// stop 시점이 비동기라서 시점이 안맞을 수도 있음. 렌더링이 바로 중단되야 함
9303		if (!this._bPlaying && !bForcePlay) {
9304			return;
9305		}
9306		
9307		var nTime = this._getDate();
9308		var nRealDuration = 0;
9309		var nFrameStep = 1; // 진행할 프레임 단계
9310		
9311		// 진행된 프레임이면 시간 계산
9312		if (this._nBeforeFrameTime !== null) {
9313			nRealDuration = nTime - this._nBeforeFrameTime; // 실제 걸린 시간
9314			nFrameStep = nSkippedFrame || Math.max(1, Math.round(nRealDuration / this._nDuration)); // 60fps 미만으로는 버린다
9315			
9316			// requestAnimationFrame 인자가 들어옴
9317			if (this._sRequestAnimationFrameName !== false) {
9318				nSkippedFrame = 0;
9319				nFrameStep = 1;
9320			}
9321			
9322			this._nSkippedFrame += Math.max(0, nFrameStep - 1);
9323			this._nFPS = Math.round(1000 / (nTime - this._nBeforeFrameTime));
9324		}
9325		
9326		this._nCurrentFrame += nFrameStep;
9327		var htInfo = this.getInfo();
9328		
9329		// callback이 없거나 callback 실행 결과가 false가 아니거나 process 이벤트 stop이 발생 안한 경우에만 진행
9330		/**
9331		 * 렌더링 진행
9332		 * @name collie.Renderer#process
9333		 * @event
9334		 * @param {Object} oEvent
9335		 * @param {Function} oEvent.stop stop 하면 렌더링이 멈춘다
9336		 */		
9337		if ((this._fCallback === null || this._fCallback(htInfo) !== false) && this.fireEvent("process", htInfo) !== false) {
9338			collie.Timer.run(this._nCurrentFrame, nRealDuration);
9339			this._update(nRealDuration);
9340			var nDebugDelayedTime = 0; 
9341			
9342			// 가상 딜레이 적용
9343			if (this.DEBUG_USE_DELAY) {
9344				nDebugDelayedTime = Math.round(Math.random() * this.DEBUG_MAX_DELAY);
9345				this._nDebugDelayedTime += nDebugDelayedTime;
9346			}
9347						
9348			this._nBeforeRenderingTime = this._getDate() - nTime;
9349			this._nBeforeFrameTime = nTime;
9350			
9351			if (this._bPlaying) {
9352				this._trigger(Math.max(0, this._nDuration - this._nBeforeRenderingTime + nDebugDelayedTime * 2));
9353			}
9354		} else {
9355			this.stop();
9356		}
9357	},
9358	
9359	/**
9360	 * 원하는 프레임으로 스킵해서 그린다
9361	 * 
9362	 * @param {Number} nSkippedFrame 값이 없으면 스킵 없이, 값이 있으면 그 값만큼 프레임을 스킵해서 그린다
9363	 */
9364	draw : function (nSkippedFrame) {
9365		this._fRender(nSkippedFrame, true);
9366	},
9367	
9368	/**
9369	 * 현재 시간을 가져 온다
9370	 * @private
9371	 * @return {Number} timestamp
9372	 */
9373	_getDate : function () {
9374		return (+new Date()) + (this.DEBUG_USE_DELAY ? this._nDebugDelayedTime : 0);
9375	},
9376
9377	/**
9378	 * 렌더링을 멈춘다
9379	 */
9380	stop : function () {
9381		if (this._bPlaying) {
9382			this._bPlaying = false;
9383			this._resetTimer();
9384			
9385			/**
9386			 * 렌더링 멈춤
9387			 * @name collie.Renderer#stop
9388			 * @event
9389			 * @param {Object} oEvent
9390			 */
9391			this.fireEvent("stop", this.getInfo());
9392
9393			this._sRenderingMode = null;
9394			this._bUseRetinaDisplay = null;			
9395			this._fCallback = null;
9396			this._nCurrentFrame = 0;
9397			this._nBeforeRenderingTime = 0;
9398			this._nSkippedFrame = 0;
9399			this._nBeforeFrameTime = null;			
9400		}
9401	},
9402	
9403	_resetTimer : function () {
9404		if (this._oRenderingTimer !== null) {
9405			if (this._sCancelAnimationFrameName !== false) {
9406				window[this._sCancelAnimationFrameName](this._oRenderingTimer);
9407			} else {
9408				clearTimeout(this._oRenderingTimer);
9409			}
9410
9411			//TODO debug			
9412			window.tempTimer = window.tempTimer || [];
9413			window.tempTimer.push(this._oRenderingTimer);
9414			this._oRenderingTimer = null;
9415		}
9416	},
9417	
9418	/**
9419	 * 잠시 멈춘다
9420	 */
9421	pause : function () {
9422		if (this._bPlaying) {
9423			this._bPlaying = false;
9424			this._bPause = true;
9425			
9426			/**
9427			 * 렌더러가 일시 정지 때 발생. getInfo 값이 이벤트 인자로 넘어간다
9428			 * @name collie.Renderer#pause
9429			 * @event
9430			 * @see collie.Renderer.getInfo
9431			 */
9432			this.fireEvent("pause", this.getInfo());
9433			
9434			// 진행되고 있는 타이머를 해제
9435			this._resetTimer();			
9436		}
9437	},
9438	
9439	/**
9440	 * 잠시 멈춘것을 다시 실행 한다
9441	 */
9442	resume : function () {
9443		if (this._bPause) {
9444			this._nBeforeFrameTime = this._getDate();
9445			this._nBeforeRenderingTime = 0;
9446			this._bPlaying = true;
9447			this._bPause = false;
9448			
9449			/**
9450			 * 렌더러가 일시 정지에서 해제될 때 발생. getInfo 값이 이벤트 인자로 넘어간다
9451			 * @name collie.Renderer#resume
9452			 * @event
9453			 * @see collie.Renderer.getInfo
9454			 */
9455			this.fireEvent("resume", this.getInfo());
9456			this._trigger(0);
9457		}
9458	},
9459	
9460	/**
9461	 * 현재 실행 중인지 여부를 반환
9462	 * 
9463	 * @return {Boolean}
9464	 */
9465	isPlaying : function () {
9466		return this._bPlaying;
9467	},
9468	
9469	/**
9470	 * 레이어 업데이트, 주로 다시 그리거나 동작 등을 업데이트 한다
9471	 * 
9472	 * @param {Number} nFrameDuration 실제 진행된 시간
9473	 * @private
9474	 */
9475	_update : function (nFrameDuration) {
9476		for (var i = 0, len = this._aLayerList.length; i < len; i++) {
9477			this._aLayerList[i].update(nFrameDuration);
9478		}
9479	},
9480	
9481	/**
9482	 * 이벤트의 레이어간 전달을 막기 위한 이벤트 상태를 설정 한다
9483	 * 
9484	 * @private
9485	 * @param {String} sEventType 이벤트 타입
9486	 * @param {Boolean} bFiredOnTarget 이벤트가 대상에 발생했는지 여부
9487	 */
9488	setEventStatus : function (sEventType, bFiredOnTarget) {
9489		this._htEventStatus = {
9490			type : sEventType,
9491			firedOnTarget : bFiredOnTarget
9492		};
9493	},
9494	
9495	/**
9496	 * 객체 이벤트를 멈춰야 하는지 여부
9497	 * @private
9498	 * @param {String} sEventType 이벤트 타입
9499	 * @return {Boolean} 이벤트를 멈춰야 하는지 여부
9500	 */
9501	isStopEvent : function (sEventType) {
9502		// click 이벤트는 임의로 발생시키기 때문에 mouseup 으로 간주
9503		if (sEventType === "click") {
9504			sEventType = "mouseup";
9505		}
9506		
9507		return sEventType === this._htEventStatus.type && this._htEventStatus.firedOnTarget;
9508	},
9509	
9510	/**
9511	 * 이벤트의 레이어간 전달을 막기 위한 이벤트 상태를 가져 온다
9512	 * 
9513	 * @private
9514	 * @return {Object} htEventStatus
9515	 * @return {String} htEventStatus.type 이벤트 타입
9516	 * @return {Boolean} htEventStatus.firedOnTarget 이벤트가 대상에 발생했는지 여부
9517	 */
9518	getEventStatus : function () {
9519		return this._htEventStatus;
9520	},
9521	
9522	/**
9523	 * 레이어 위에서 기본 이벤트(mousemove, mousedown) 동작을 막을지 여부를 설정 한다.
9524	 * 
9525	 * @param {Boolean} bPreventDefault true면 기본 동작을 막는다.
9526	 */
9527	setPreventDefault : function (bPreventDefault) {
9528		this._bIsPreventDefault = !!bPreventDefault;
9529	},
9530	
9531	/**
9532	 * 기본 동작을 막는지 여부를 반환
9533	 * 
9534	 * @return {Boolean} true일 때 막는다, 기본값이 true
9535	 */
9536	isPreventDefault : function () {
9537		return this._bIsPreventDefault;
9538	},
9539	
9540	/**
9541	 * 렌더러에 등록된 모든 레이어의 크기를 변경 한다
9542	 * 
9543	 * @param {Number} nWidth
9544	 * @param {Number} nHeight
9545	 * @param {Boolean} bExpand 확장할지 크기만 변경할지 여부
9546	 */
9547	resize : function (nWidth, nHeight, bExpand) {
9548		for (var i = 0, len = this._aLayerList.length; i < len; i++) {
9549			this._aLayerList[i].resize(nWidth, nHeight, bExpand);
9550		}
9551	}
9552}, collie.Component))();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.