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 })();