1 
  2 /**
  3  * @class View类是所有可视对象或组件的基类。
  4  * @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。
  5  * @module hilo/view/View
  6  * @requires hilo/core/Hilo
  7  * @requires hilo/core/Class
  8  * @requires hilo/event/EventMixin
  9  * @property {String} id 可视对象的唯一标识符。
 10  * @property {Number} x 可视对象的x轴坐标。默认值为0。
 11  * @property {Number} y 可视对象的y轴坐标。默认值为0。
 12  * @property {Number} width 可视对象的宽度。默认值为0。
 13  * @property {Number} height 可视对象的高度。默认值为0。
 14  * @property {Number} alpha 可视对象的透明度。默认值为1。
 15  * @property {Number} rotation 可视对象的旋转角度。默认值为0。
 16  * @property {Boolean} visible 可视对象是否可见。默认为可见,即true。
 17  * @property {Number} pivotX 可视对象的中心点的x轴坐标。默认值为0。
 18  * @property {Number} pivotY 可视对象的中心点的y轴坐标。默认值为0。
 19  * @property {Number} scaleX 可视对象在x轴上的缩放比例。默认为不缩放,即1。
 20  * @property {Number} scaleY 可视对象在y轴上的缩放比例。默认为不缩放,即1。
 21  * @property {Boolean} pointerEnabled 可视对象是否接受交互事件。默认为接受交互事件,即true。
 22  * @property {Container} parent 可视对象的父容器。只读属性。
 23  * @property {Stage} stage 可视对象的舞台实例。只读属性。
 24  * @property {Number} depth 可视对象的深度,也即z轴的序号。只读属性。
 25  * @property {Number} measuredWidth 可视对象的测量宽度。只读属性。
 26  * @property {Number} measuredHeight 可视对象的测量高度。只读属性。
 27  * @property {Drawable} drawable 可视对象的可绘制对象。
 28  * @property {Array} boundsArea 可视对象的区域顶点数组。格式为:[{x:10, y:10}, {x:20, y:20}]。
 29  */
 30 var View = (function(){
 31 
 32 return Class.create(/** @lends View.prototype */{
 33     Mixes: EventMixin,
 34     constructor: function View(properties){
 35         properties = properties || {};
 36         this.id = this.id || properties.id || Hilo.getUid("View");
 37         Hilo.copy(this, properties, true);
 38     },
 39 
 40     id: null,
 41     x: 0,
 42     y: 0,
 43     width: 0,
 44     height: 0,
 45     alpha: 1,
 46     rotation: 0,
 47     visible: true,
 48     pivotX: 0,
 49     pivotY: 0,
 50     scaleX: 1,
 51     scaleY: 1,
 52     pointerEnabled: true,
 53     drawable: null,
 54     boundsArea: null,
 55     parent: null,
 56     depth: -1,
 57 
 58     stage: {
 59         get: function(){
 60             var obj = this, parent;
 61             while(parent = obj.parent) obj = parent;
 62             //NOTE: don't use `instanceof` to prevent circular module requirement.
 63             //But it's not a very reliable way to check it's a stage instance.
 64             if(obj.canvas) return obj;
 65             return null;
 66         }
 67     },
 68     
 69     measuredWidth: {
 70         get: function(){
 71             return this.width * this.scaleX;
 72         }   
 73     },
 74     measuredHeight: {
 75         get: function(){
 76             return this.height * this.scaleY;
 77         }
 78     },
 79 
 80     /**
 81      * 添加此对象到父容器。
 82      * @param {Container} container 一个容器。
 83      * @param {Uint} index 要添加到索引位置。
 84      * @returns 可视对象本身。
 85      */
 86     addTo: function(container, index){
 87         if(typeof index === 'number') container.addChildAt(this, index);
 88         else container.addChild(this);
 89         return this;
 90     },
 91 
 92     /**
 93      * 从父容器里删除此对象。
 94      * @returns 可视对象本身。
 95      */
 96     removeFromParent: function(){
 97         var parent = this.parent;
 98         if(parent) parent.removeChild(this);
 99         return this;
100     },
101 
102     /**
103      * 设置背景填充样式。限制:DOMRenderer仅支持CSS颜色填充样式。
104      * @param {Object} fillStyle 填充样式。可以是CSS颜色值、canvas的gradient或pattern填充。
105      * @param {Number} x 背景填充的起始点的x轴坐标。
106      * @param {Number} y 背景填充的起始点的y轴坐标。
107      * @param {Number} width 背景填充的区域宽度。
108      * @param {Number} height 背景填充的区域高度。
109      * @returns 可视对象本身。
110      */
111     setBgFill: function(fillStyle, x, y, width, height){
112         if(fillStyle){
113             var bgFill = this.bgFill || (this.bgFill = {});
114             bgFill.style = fillStyle;
115             bgFill.x = x || 0;
116             bgFill.y = y || 0;
117             if(width) bgFill.width = width;
118             if(height) bgFill.height = height;
119         }else{
120             this.bgFill = null;
121         }
122         return this;
123     },
124 
125     /**
126      * 获取可视对象在舞台全局坐标系内的外接矩形以及所有顶点坐标。
127      * @returns {Array} 可视对象的顶点坐标数组vertexs。另vertexs还包含属性:
128      * <ul>
129      * <li><b>x</b> - 可视对象的外接矩形x轴坐标。</li>
130      * <li><b>y</b> - 可视对象的外接矩形y轴坐标。</li>
131      * <li><b>width</b> - 可视对象的外接矩形的宽度。</li>
132      * <li><b>height</b> - 可视对象的外接矩形的高度。</li>
133      * </ul>
134      */
135     getBounds: function(){
136         var w = this.width, h = this.height,
137             mtx = this.getConcatenatedMatrix(),
138             poly = this.boundsArea || [{x:0, y:0}, {x:w, y:0}, {x:w, y:h}, {x:0, y:h}],
139             vertexs = [], point, x, y, minX, maxX, minY, maxY;
140         
141         for(var i = 0, len = poly.length; i < len; i++){
142             point = mtx.transformPoint(poly[i], true, true);
143             x = point.x;
144             y = point.y;
145 
146             if(i == 0){
147                 minX = maxX = x;
148                 minY = maxY = y;
149             }else{
150                 if(minX > x) minX = x;
151                 else if(maxX < x) maxX = x;
152                 if(minY > y) minY = y;
153                 else if(maxY < y) maxY = y;
154             }
155             vertexs[i] = point;
156         }
157         
158         vertexs.x = minX;
159         vertexs.y = minY;
160         vertexs.width = maxX - minX;
161         vertexs.height = maxY - minY;
162         return vertexs;
163     },
164 
165     /**
166      * 获取可视对象相对于其某个祖先(默认为舞台Stage)的连接矩阵。
167      * @param {View} ancestor 可视对象的相对的祖先容器。
168      * @private
169      */
170     getConcatenatedMatrix: function(ancestor){
171         var mtx = new Matrix(1, 0, 0, 1, 0, 0);
172 
173         if(ancestor !== this){
174             for(var o = this; o.parent && o.parent != ancestor; o = o.parent){       
175                 var cos = 1, sin = 0, 
176                     rotation = o.rotation % 360,
177                     pivotX = o.pivotX, pivotY = o.pivotY, 
178                     scaleX = o.scaleX, scaleY = o.scaleY;
179                 
180                 if(rotation){
181                     var r = rotation * Math.PI / 180;
182                     cos = Math.cos(r);
183                     sin = Math.sin(r);
184                 }
185                 
186                 if(pivotX != 0) mtx.tx -= pivotX;
187                 if(pivotY != 0) mtx.ty -= pivotY;
188                 mtx.concat(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, o.x, o.y);
189             }
190         }
191         return mtx;
192     },
193 
194     /**
195      * 检测由x和y参数指定的点是否在其外接矩形之内。
196      * @param {Number} x 要检测的点的x轴坐标。
197      * @param {Number} y 要检测的点的y轴坐标。
198      * @param {Boolean} usePolyCollision 是否使用多边形碰撞检测。默认为false。
199      * @returns {Boolean} 点是否在可视对象之内。
200      */
201     hitTestPoint: function(x, y, usePolyCollision){
202         var bound = this.getBounds(),
203             hit = x >= bound.x && x <= bound.x + bound.width && 
204                   y >= bound.y && y <= bound.y + bound.height;
205         
206         if(hit && usePolyCollision){
207             hit = pointInPolygon(x, y, bound);
208         }
209         return hit;
210     },
211 
212     /**
213      * 检测object参数指定的对象是否与其相交。
214      * @param {View} object 要检测的可视对象。
215      * @param {Boolean} usePolyCollision 是否使用多边形碰撞检测。默认为false。
216      */
217     hitTestObject: function(object, usePolyCollision){
218         var b1 = this.getBounds(), 
219             b2 = object.getBounds(),
220             hit = b1.x <= b2.x + b2.width && b2.x <= b1.x + b1.width && 
221                   b1.y <= b2.y + b2.height && b2.y <= b1.y + b1.height;
222         
223         if(hit && usePolyCollision){
224             hit = polygonCollision(b1, b2);
225         }
226         return !!hit;
227     },
228 
229     /**
230      * 可视对象的基本渲染实现,用于框架内部或高级开发使用。通常应该重写render方法。
231      * @param {Renderer} renderer 渲染器。
232      * @param {Number} delta 渲染时时间偏移量。
233      * @protected
234      */
235     _render: function(renderer, delta){
236         if((!this.onUpdate || this.onUpdate(delta) !== false) && renderer.startDraw(this)){
237             renderer.transform(this);
238             this.render(renderer, delta);
239             renderer.endDraw(this);
240         }
241     },
242 
243     /**
244      * 更新可视对象,此方法会在可视对象渲染之前调用。此函数可以返回一个Boolean值。若返回false,则此对象不会渲染。默认值为null。
245      * 限制:如果在此函数中改变了可视对象在其父容器中的层级,当前渲染帧并不会正确渲染,而是在下一渲染帧。可在其父容器的onUpdate方法中来实现。
246      * @type Function
247      * @default null
248      */
249     onUpdate: null,
250     
251     /**
252      * 可视对象的具体渲染逻辑。子类可通过覆盖此方法实现自己的渲染。
253      * @param {Renderer} renderer 渲染器。
254      * @param {Number} delta 渲染时时间偏移量。
255      */
256     render: function(renderer, delta){
257         renderer.draw(this);
258     },
259 
260     /**
261      * 返回可视对象的字符串表示。
262      * @returns {String} 可视对象的字符串表示。
263      */
264     toString: function(){
265         return Hilo.viewToString(this);
266     }
267 });
268 
269 /**
270  * @private
271  */
272 function pointInPolygon(x, y, poly){
273     var cross = 0, onBorder = false, minX, maxX, minY, maxY;        
274     
275     for(var i = 0, len = poly.length; i < len; i++){
276         var p1 = poly[i], p2 = poly[(i+1)%len];           
277         
278         if(p1.y == p2.y && y == p1.y){
279             p1.x > p2.x ? (minX = p2.x, maxX = p1.x) : (minX = p1.x, maxX = p2.x);
280             if(x >= minX && x <= maxX){
281                 onBorder = true;
282                 continue;
283             }
284         }
285         
286         p1.y > p2.y ? (minY = p2.y, maxY = p1.y) : (minY = p1.y, maxY = p2.y);
287         if(y < minY || y > maxY) continue;
288         
289         var nx = (y - p1.y)*(p2.x - p1.x) / (p2.y - p1.y) + p1.x;
290         if(nx > x) cross++;
291         else if(nx == x) onBorder = true;        
292     }
293     
294     return onBorder || (cross % 2 == 1);
295 }
296 
297 /**
298  * @private
299  */
300 function polygonCollision(poly1, poly2){   
301     var result = doSATCheck(poly1, poly2, {overlap:-Infinity, normal:{x:0, y:0}});
302     if(result) return doSATCheck(poly2, poly1, result);
303     return false;
304 }
305 
306 /**
307  * @private
308  */
309 function doSATCheck(poly1, poly2, result){
310     var len1 = poly1.length, len2 = poly2.length, 
311         currentPoint, nextPoint, distance, 
312         min1, max1, min2, max2, dot, overlap, normal = {x:0, y:0};
313     
314     for(var i = 0; i < len1; i++){
315         currentPoint = poly1[i];
316         nextPoint = poly1[(i < len1-1 ? i+1 : 0)];
317         
318         normal.x = currentPoint.y - nextPoint.y;
319         normal.y = nextPoint.x - currentPoint.x;
320         
321         distance = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
322         normal.x /= distance;
323         normal.y /= distance;
324         
325         min1 = max1 = poly1[0].x * normal.x + poly1[0].y * normal.y;        
326         for(var j = 1; j < len1; j++){
327             dot = poly1[j].x * normal.x + poly1[j].y * normal.y;
328             if(dot > max1) max1 = dot;
329             else if(dot < min1) min1 = dot;
330         }
331         
332         min2 = max2 = poly2[0].x * normal.x + poly2[0].y * normal.y;        
333         for(j = 1; j < len2; j++){
334             dot = poly2[j].x * normal.x + poly2[j].y * normal.y;
335             if(dot > max2) max2 = dot;
336             else if(dot < min2) min2 = dot;
337         }
338         
339         if(min1 < min2){
340             overlap = min2 - max1;
341             normal.x = -normal.x;
342             normal.y = -normal.y;
343         }else{
344             overlap = min1 - max2;
345         }
346         
347         if(overlap >= 0){
348             return false;
349         }else if(overlap > result.overlap){
350             result.overlap = overlap;
351             result.normal.x = normal.x;
352             result.normal.y = normal.y;
353         }
354     }
355     
356     return result;
357 }
358 
359 })();